Просмотр исходного кода

ux: Improvements to to-do lists and task headers (#9096)

Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com>
Co-authored-by: Matt Rubens <[email protected]>
Bruno Bergher 1 месяц назад
Родитель
Сommit
fff5cf804f
28 измененных файлов с 536 добавлено и 447 удалено
  1. 40 14
      webview-ui/src/components/chat/ChatRow.tsx
  2. 9 14
      webview-ui/src/components/chat/CloudTaskButton.tsx
  3. 50 0
      webview-ui/src/components/chat/LucideIconButton.tsx
  4. 1 1
      webview-ui/src/components/chat/Mention.tsx
  5. 14 32
      webview-ui/src/components/chat/ShareButton.tsx
  6. 23 24
      webview-ui/src/components/chat/TaskActions.tsx
  7. 65 38
      webview-ui/src/components/chat/TaskHeader.tsx
  8. 131 0
      webview-ui/src/components/chat/TodoChangeDisplay.tsx
  9. 53 304
      webview-ui/src/components/chat/TodoListDisplay.tsx
  10. 8 1
      webview-ui/src/i18n/locales/ca/chat.json
  11. 8 1
      webview-ui/src/i18n/locales/de/chat.json
  12. 7 0
      webview-ui/src/i18n/locales/en/chat.json
  13. 8 1
      webview-ui/src/i18n/locales/es/chat.json
  14. 8 1
      webview-ui/src/i18n/locales/fr/chat.json
  15. 8 1
      webview-ui/src/i18n/locales/hi/chat.json
  16. 8 1
      webview-ui/src/i18n/locales/id/chat.json
  17. 8 1
      webview-ui/src/i18n/locales/it/chat.json
  18. 8 1
      webview-ui/src/i18n/locales/ja/chat.json
  19. 8 1
      webview-ui/src/i18n/locales/ko/chat.json
  20. 8 1
      webview-ui/src/i18n/locales/nl/chat.json
  21. 8 1
      webview-ui/src/i18n/locales/pl/chat.json
  22. 8 1
      webview-ui/src/i18n/locales/pt-BR/chat.json
  23. 8 1
      webview-ui/src/i18n/locales/ru/chat.json
  24. 8 1
      webview-ui/src/i18n/locales/tr/chat.json
  25. 8 1
      webview-ui/src/i18n/locales/vi/chat.json
  26. 8 1
      webview-ui/src/i18n/locales/zh-CN/chat.json
  27. 8 1
      webview-ui/src/i18n/locales/zh-TW/chat.json
  28. 7 3
      webview-ui/src/index.css

+ 40 - 14
webview-ui/src/components/chat/ChatRow.tsx

@@ -18,6 +18,7 @@ import { formatPathTooltip } from "@src/utils/formatPathTooltip"
 
 
 import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
 import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
 import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
 import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
+import { TodoChangeDisplay } from "./TodoChangeDisplay"
 import CodeAccordian from "../common/CodeAccordian"
 import CodeAccordian from "../common/CodeAccordian"
 import MarkdownBlock from "../common/MarkdownBlock"
 import MarkdownBlock from "../common/MarkdownBlock"
 import { ReasoningBlock } from "./ReasoningBlock"
 import { ReasoningBlock } from "./ReasoningBlock"
@@ -62,6 +63,39 @@ import {
 import { cn } from "@/lib/utils"
 import { cn } from "@/lib/utils"
 import { PathTooltip } from "../ui/PathTooltip"
 import { PathTooltip } from "../ui/PathTooltip"
 
 
+// Helper function to get previous todos before a specific message
+function getPreviousTodos(messages: ClineMessage[], currentMessageTs: number): any[] {
+	// Find the previous updateTodoList message before the current one
+	const previousUpdateIndex = messages
+		.slice()
+		.reverse()
+		.findIndex((msg) => {
+			if (msg.ts >= currentMessageTs) return false
+			if (msg.type === "ask" && msg.ask === "tool") {
+				try {
+					const tool = JSON.parse(msg.text || "{}")
+					return tool.tool === "updateTodoList"
+				} catch {
+					return false
+				}
+			}
+			return false
+		})
+
+	if (previousUpdateIndex !== -1) {
+		const previousMessage = messages.slice().reverse()[previousUpdateIndex]
+		try {
+			const tool = JSON.parse(previousMessage.text || "{}")
+			return tool.todos || []
+		} catch {
+			return []
+		}
+	}
+
+	// If no previous updateTodoList message, return empty array
+	return []
+}
+
 interface ChatRowProps {
 interface ChatRowProps {
 	message: ClineMessage
 	message: ClineMessage
 	lastModifiedMessage?: ClineMessage
 	lastModifiedMessage?: ClineMessage
@@ -127,11 +161,10 @@ export const ChatRowContent = ({
 	onFollowUpUnmount,
 	onFollowUpUnmount,
 	onBatchFileResponse,
 	onBatchFileResponse,
 	isFollowUpAnswered,
 	isFollowUpAnswered,
-	editable,
 }: ChatRowContentProps) => {
 }: ChatRowContentProps) => {
 	const { t } = useTranslation()
 	const { t } = useTranslation()
 
 
-	const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration } = useExtensionState()
+	const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration, clineMessages } = useExtensionState()
 	const { info: model } = useSelectedModel(apiConfiguration)
 	const { info: model } = useSelectedModel(apiConfiguration)
 	const [isEditing, setIsEditing] = useState(false)
 	const [isEditing, setIsEditing] = useState(false)
 	const [editedContent, setEditedContent] = useState("")
 	const [editedContent, setEditedContent] = useState("")
@@ -503,18 +536,11 @@ export const ChatRowContent = ({
 			}
 			}
 			case "updateTodoList" as any: {
 			case "updateTodoList" as any: {
 				const todos = (tool as any).todos || []
 				const todos = (tool as any).todos || []
-				return (
-					<UpdateTodoListToolBlock
-						todos={todos}
-						content={tool.content}
-						onChange={(updatedTodos) => {
-							if (typeof vscode !== "undefined" && vscode?.postMessage) {
-								vscode.postMessage({ type: "updateTodoList", payload: { todos: updatedTodos } })
-							}
-						}}
-						editable={editable && isLast}
-					/>
-				)
+
+				// Get previous todos from the latest todos in the task context
+				const previousTodos = getPreviousTodos(clineMessages, message.ts)
+
+				return <TodoChangeDisplay previousTodos={previousTodos} newTodos={todos} />
 			}
 			}
 			case "newFileCreated":
 			case "newFileCreated":
 				return (
 				return (

+ 9 - 14
webview-ui/src/components/chat/CloudTaskButton.tsx

@@ -1,14 +1,15 @@
 import { useState, useEffect, useCallback } from "react"
 import { useState, useEffect, useCallback } from "react"
 import { useTranslation } from "react-i18next"
 import { useTranslation } from "react-i18next"
-import { CloudUpload, Copy, Check } from "lucide-react"
+import { Copy, Check, CloudUploadIcon } from "lucide-react"
 import QRCode from "qrcode"
 import QRCode from "qrcode"
 
 
 import type { HistoryItem } from "@roo-code/types"
 import type { HistoryItem } from "@roo-code/types"
 
 
 import { useExtensionState } from "@/context/ExtensionStateContext"
 import { useExtensionState } from "@/context/ExtensionStateContext"
 import { useCopyToClipboard } from "@/utils/clipboard"
 import { useCopyToClipboard } from "@/utils/clipboard"
-import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input, StandardTooltip } from "@/components/ui"
+import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input } from "@/components/ui"
 import { vscode } from "@/utils/vscode"
 import { vscode } from "@/utils/vscode"
+import { LucideIconButton } from "./LucideIconButton"
 
 
 interface CloudTaskButtonProps {
 interface CloudTaskButtonProps {
 	item?: HistoryItem
 	item?: HistoryItem
@@ -83,18 +84,12 @@ export const CloudTaskButton = ({ item, disabled = false }: CloudTaskButtonProps
 
 
 	return (
 	return (
 		<>
 		<>
-			<StandardTooltip content={t("chat:task.openInCloud")}>
-				<Button
-					variant="ghost"
-					size="icon"
-					disabled={disabled}
-					className="h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
-					onClick={() => setDialogOpen(true)}
-					data-testid="cloud-task-button"
-					aria-label={t("chat:task.openInCloud")}>
-					<CloudUpload className="h-4 w-4" />
-				</Button>
-			</StandardTooltip>
+			<LucideIconButton
+				icon={CloudUploadIcon}
+				disabled={disabled}
+				data-testid="cloud-task-button"
+				title={t("chat:task.openInCloud")}
+				onClick={() => setDialogOpen(true)}></LucideIconButton>
 
 
 			<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
 			<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
 				<DialogContent className="max-w-100">
 				<DialogContent className="max-w-100">

+ 50 - 0
webview-ui/src/components/chat/LucideIconButton.tsx

@@ -0,0 +1,50 @@
+import { cn } from "@src/lib/utils"
+import { Button, StandardTooltip } from "@src/components/ui"
+import { Loader2, LucideIcon } from "lucide-react"
+
+interface LucideIconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
+	icon: LucideIcon
+	title: string
+	disabled?: boolean
+	tooltip?: boolean
+	isLoading?: boolean
+	style?: React.CSSProperties
+}
+
+export const LucideIconButton: React.FC<LucideIconButtonProps> = ({
+	icon,
+	title,
+	className,
+	disabled,
+	tooltip = true,
+	isLoading,
+	onClick,
+	style,
+	...props
+}) => {
+	const Icon = icon
+	return (
+		<StandardTooltip content={tooltip ? title : undefined}>
+			<Button
+				aria-label={title}
+				className={cn(
+					"relative inline-flex items-center justify-center",
+					"bg-transparent border-none p-1.5",
+					"rounded-full",
+					"text-vscode-foreground opacity-85",
+					"transition-all duration-150",
+					"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
+					"active:bg-[rgba(255,255,255,0.1)]",
+					!disabled && "cursor-pointer hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)]",
+					disabled && "cursor-not-allowed opacity-40 hover:bg-transparent active:bg-transparent",
+					className,
+				)}
+				disabled={disabled}
+				onClick={!disabled ? onClick : undefined}
+				style={{ fontSize: 16.5, ...style }}
+				{...props}>
+				{isLoading ? <Loader2 className="size-2.5 animate-spin" /> : <Icon className="size-2.5" />}
+			</Button>
+		</StandardTooltip>
+	)
+}

+ 1 - 1
webview-ui/src/components/chat/Mention.tsx

@@ -21,7 +21,7 @@ export const Mention = ({ text, withShadow = false }: MentionProps) => {
 			return (
 			return (
 				<span
 				<span
 					key={index}
 					key={index}
-					className={`${withShadow ? "mention-context-highlight-with-shadow" : "mention-context-highlight"} cursor-pointer`}
+					className={`${withShadow ? "mention-context-highlight-with-shadow" : "mention-context-highlight"} text-[0.9em] cursor-pointer`}
 					onClick={() => vscode.postMessage({ type: "openMention", text: part })}>
 					onClick={() => vscode.postMessage({ type: "openMention", text: part })}>
 					@{part}
 					@{part}
 				</span>
 				</span>

+ 14 - 32
webview-ui/src/components/chat/ShareButton.tsx

@@ -1,6 +1,6 @@
 import { useState, useEffect } from "react"
 import { useState, useEffect } from "react"
 import { useTranslation } from "react-i18next"
 import { useTranslation } from "react-i18next"
-import { Share2 } from "lucide-react"
+import { Share2Icon } from "lucide-react"
 
 
 import { type HistoryItem, type ShareVisibility, TelemetryEventName } from "@roo-code/types"
 import { type HistoryItem, type ShareVisibility, TelemetryEventName } from "@roo-code/types"
 
 
@@ -10,7 +10,6 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
 import { useCloudUpsell } from "@/hooks/useCloudUpsell"
 import { useCloudUpsell } from "@/hooks/useCloudUpsell"
 import { CloudUpsellDialog } from "@/components/cloud/CloudUpsellDialog"
 import { CloudUpsellDialog } from "@/components/cloud/CloudUpsellDialog"
 import {
 import {
-	Button,
 	Popover,
 	Popover,
 	PopoverContent,
 	PopoverContent,
 	PopoverTrigger,
 	PopoverTrigger,
@@ -20,14 +19,14 @@ import {
 	CommandGroup,
 	CommandGroup,
 	StandardTooltip,
 	StandardTooltip,
 } from "@/components/ui"
 } from "@/components/ui"
+import { LucideIconButton } from "./LucideIconButton"
 
 
 interface ShareButtonProps {
 interface ShareButtonProps {
 	item?: HistoryItem
 	item?: HistoryItem
 	disabled?: boolean
 	disabled?: boolean
-	showLabel?: boolean
 }
 }
 
 
-export const ShareButton = ({ item, disabled = false, showLabel = false }: ShareButtonProps) => {
+export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
 	const [shareDropdownOpen, setShareDropdownOpen] = useState(false)
 	const [shareDropdownOpen, setShareDropdownOpen] = useState(false)
 	const [shareSuccess, setShareSuccess] = useState<{ visibility: ShareVisibility; url: string } | null>(null)
 	const [shareSuccess, setShareSuccess] = useState<{ visibility: ShareVisibility; url: string } | null>(null)
 	const [wasConnectInitiatedFromShare, setWasConnectInitiatedFromShare] = useState(false)
 	const [wasConnectInitiatedFromShare, setWasConnectInitiatedFromShare] = useState(false)
@@ -153,20 +152,13 @@ export const ShareButton = ({ item, disabled = false, showLabel = false }: Share
 				<Popover open={shareDropdownOpen} onOpenChange={setShareDropdownOpen}>
 				<Popover open={shareDropdownOpen} onOpenChange={setShareDropdownOpen}>
 					<StandardTooltip content={shareButtonState.title}>
 					<StandardTooltip content={shareButtonState.title}>
 						<PopoverTrigger asChild>
 						<PopoverTrigger asChild>
-							<Button
-								variant="ghost"
-								size={showLabel ? "sm" : "icon"}
+							<LucideIconButton
+								icon={Share2Icon}
 								disabled={disabled || shareButtonState.disabled}
 								disabled={disabled || shareButtonState.disabled}
-								className={
-									showLabel
-										? "h-7 px-2 hover:bg-vscode-toolbar-hoverBackground"
-										: "h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
-								}
+								tooltip={false}
 								onClick={handleShareButtonClick}
 								onClick={handleShareButtonClick}
-								data-testid="share-button">
-								<Share2 />
-								{showLabel && <span className="ml-0">{t("chat:task.share")}</span>}
-							</Button>
+								data-testid="share-button"
+								title={t("chat:task.share")}></LucideIconButton>
 						</PopoverTrigger>
 						</PopoverTrigger>
 					</StandardTooltip>
 					</StandardTooltip>
 
 
@@ -221,22 +213,12 @@ export const ShareButton = ({ item, disabled = false, showLabel = false }: Share
 					</PopoverContent>
 					</PopoverContent>
 				</Popover>
 				</Popover>
 			) : (
 			) : (
-				<StandardTooltip content={shareButtonState.title}>
-					<Button
-						variant="ghost"
-						size={showLabel ? "sm" : "icon"}
-						disabled={disabled || shareButtonState.disabled}
-						className={
-							showLabel
-								? "h-7 px-2 hover:bg-vscode-toolbar-hoverBackground"
-								: "h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
-						}
-						onClick={handleShareButtonClick}
-						data-testid="share-button">
-						<Share2 />
-						{showLabel && <span className="ml-1">{t("chat:task.share")}</span>}
-					</Button>
-				</StandardTooltip>
+				<LucideIconButton
+					icon={Share2Icon}
+					disabled={disabled || shareButtonState.disabled}
+					title={shareButtonState.title}
+					onClick={handleShareButtonClick}
+					data-testid="share-button"></LucideIconButton>
 			)}
 			)}
 
 
 			{/* Connect to Cloud Modal */}
 			{/* Connect to Cloud Modal */}

+ 23 - 24
webview-ui/src/components/chat/TaskActions.tsx

@@ -7,9 +7,10 @@ import { vscode } from "@/utils/vscode"
 import { useCopyToClipboard } from "@/utils/clipboard"
 import { useCopyToClipboard } from "@/utils/clipboard"
 
 
 import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
 import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
-import { IconButton } from "./IconButton"
 import { ShareButton } from "./ShareButton"
 import { ShareButton } from "./ShareButton"
 import { CloudTaskButton } from "./CloudTaskButton"
 import { CloudTaskButton } from "./CloudTaskButton"
+import { CopyIcon, DownloadIcon, Trash2Icon } from "lucide-react"
+import { LucideIconButton } from "./LucideIconButton"
 
 
 interface TaskActionsProps {
 interface TaskActionsProps {
 	item?: HistoryItem
 	item?: HistoryItem
@@ -19,40 +20,38 @@ interface TaskActionsProps {
 export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
 export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
 	const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
 	const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
 	const { t } = useTranslation()
 	const { t } = useTranslation()
-	const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard()
+	const { copyWithFeedback } = useCopyToClipboard()
 
 
 	return (
 	return (
-		<div className="flex flex-row items-center">
-			<IconButton
-				iconClass="codicon-desktop-download"
+		<div className="flex flex-row items-center -ml-0.5 mt-1 gap-1">
+			<LucideIconButton
+				icon={DownloadIcon}
 				title={t("chat:task.export")}
 				title={t("chat:task.export")}
 				onClick={() => vscode.postMessage({ type: "exportCurrentTask" })}
 				onClick={() => vscode.postMessage({ type: "exportCurrentTask" })}
 			/>
 			/>
+
 			{item?.task && (
 			{item?.task && (
-				<IconButton
-					iconClass={showCopyFeedback ? "codicon-check" : "codicon-copy"}
+				<LucideIconButton
+					icon={CopyIcon}
 					title={t("history:copyPrompt")}
 					title={t("history:copyPrompt")}
 					onClick={(e) => copyWithFeedback(item.task, e)}
 					onClick={(e) => copyWithFeedback(item.task, e)}
 				/>
 				/>
 			)}
 			)}
 			{!!item?.size && item.size > 0 && (
 			{!!item?.size && item.size > 0 && (
 				<>
 				<>
-					<div className="flex items-center">
-						<IconButton
-							iconClass="codicon-trash"
-							title={t("chat:task.delete")}
-							disabled={buttonsDisabled}
-							onClick={(e) => {
-								e.stopPropagation()
-
-								if (e.shiftKey) {
-									vscode.postMessage({ type: "deleteTaskWithId", text: item.id })
-								} else {
-									setDeleteTaskId(item.id)
-								}
-							}}
-						/>
-					</div>
+					<LucideIconButton
+						icon={Trash2Icon}
+						title={t("chat:task.delete")}
+						disabled={buttonsDisabled}
+						onClick={(e) => {
+							e.stopPropagation()
+							if (e.shiftKey) {
+								vscode.postMessage({ type: "deleteTaskWithId", text: item.id })
+							} else {
+								setDeleteTaskId(item.id)
+							}
+						}}
+					/>
 					{deleteTaskId && (
 					{deleteTaskId && (
 						<DeleteTaskDialog
 						<DeleteTaskDialog
 							taskId={deleteTaskId}
 							taskId={deleteTaskId}
@@ -62,7 +61,7 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
 					)}
 					)}
 				</>
 				</>
 			)}
 			)}
-			<ShareButton item={item} disabled={false} showLabel={false} />
+			<ShareButton item={item} disabled={false} />
 			<CloudTaskButton item={item} disabled={buttonsDisabled} />
 			<CloudTaskButton item={item} disabled={buttonsDisabled} />
 		</div>
 		</div>
 	)
 	)

+ 65 - 38
webview-ui/src/components/chat/TaskHeader.tsx

@@ -3,7 +3,15 @@ import { useTranslation } from "react-i18next"
 import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
 import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
 import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
 import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
 import DismissibleUpsell from "@src/components/common/DismissibleUpsell"
 import DismissibleUpsell from "@src/components/common/DismissibleUpsell"
-import { FoldVertical, ChevronUp, ChevronDown } from "lucide-react"
+import {
+	ChevronUp,
+	ChevronDown,
+	SquarePen,
+	Coins,
+	HardDriveDownload,
+	HardDriveUpload,
+	FoldVerticalIcon,
+} from "lucide-react"
 import prettyBytes from "pretty-bytes"
 import prettyBytes from "pretty-bytes"
 
 
 import type { ClineMessage } from "@roo-code/types"
 import type { ClineMessage } from "@roo-code/types"
@@ -23,6 +31,7 @@ import { TaskActions } from "./TaskActions"
 import { ContextWindowProgress } from "./ContextWindowProgress"
 import { ContextWindowProgress } from "./ContextWindowProgress"
 import { Mention } from "./Mention"
 import { Mention } from "./Mention"
 import { TodoListDisplay } from "./TodoListDisplay"
 import { TodoListDisplay } from "./TodoListDisplay"
+import { LucideIconButton } from "./LucideIconButton"
 
 
 export interface TaskHeaderProps {
 export interface TaskHeaderProps {
 	task: ClineMessage
 	task: ClineMessage
@@ -87,20 +96,18 @@ const TaskHeader = ({
 	const contextWindow = model?.contextWindow || 1
 	const contextWindow = model?.contextWindow || 1
 
 
 	const condenseButton = (
 	const condenseButton = (
-		<StandardTooltip content={t("chat:task.condenseContext")}>
-			<button
-				disabled={buttonsDisabled}
-				onClick={() => currentTaskItem && handleCondenseContext(currentTaskItem.id)}
-				className="shrink-0 min-h-[20px] min-w-[20px] p-[2px] cursor-pointer disabled:cursor-not-allowed opacity-85 hover:opacity-100 bg-transparent border-none rounded-md">
-				<FoldVertical size={16} />
-			</button>
-		</StandardTooltip>
+		<LucideIconButton
+			title={t("chat:task.condenseContext")}
+			icon={FoldVerticalIcon}
+			disabled={buttonsDisabled}
+			onClick={() => currentTaskItem && handleCondenseContext(currentTaskItem.id)}
+		/>
 	)
 	)
 
 
 	const hasTodos = todos && Array.isArray(todos) && todos.length > 0
 	const hasTodos = todos && Array.isArray(todos) && todos.length > 0
 
 
 	return (
 	return (
-		<div className="pt-2 pb-0 px-3">
+		<div className="group pt-2 pb-0 px-3">
 			{showLongRunningTaskMessage && !isTaskComplete && (
 			{showLongRunningTaskMessage && !isTaskComplete && (
 				<DismissibleUpsell
 				<DismissibleUpsell
 					upsellId="longRunningTask"
 					upsellId="longRunningTask"
@@ -115,10 +122,15 @@ const TaskHeader = ({
 					"px-3 pt-2.5 pb-2 flex flex-col gap-1.5 relative z-1 cursor-pointer",
 					"px-3 pt-2.5 pb-2 flex flex-col gap-1.5 relative z-1 cursor-pointer",
 					"bg-vscode-input-background hover:bg-vscode-input-background/90",
 					"bg-vscode-input-background hover:bg-vscode-input-background/90",
 					"text-vscode-foreground/80 hover:text-vscode-foreground",
 					"text-vscode-foreground/80 hover:text-vscode-foreground",
-					"shadow-sm shadow-black/30 rounded-xl",
+					"shadow-lg shadow-vscode-sideBar-background/50 rounded-xl",
 					hasTodos && "border-b-0",
 					hasTodos && "border-b-0",
 				)}
 				)}
 				onClick={(e) => {
 				onClick={(e) => {
+					// Don't expand if clicking on todos section
+					if (e.target instanceof Element && e.target.closest("[data-todo-list]")) {
+						return
+					}
+
 					// Don't expand if clicking on buttons or interactive elements
 					// Don't expand if clicking on buttons or interactive elements
 					if (
 					if (
 						e.target instanceof Element &&
 						e.target instanceof Element &&
@@ -142,12 +154,14 @@ const TaskHeader = ({
 				}}>
 				}}>
 				<div className="flex justify-between items-center gap-0">
 				<div className="flex justify-between items-center gap-0">
 					<div className="flex items-center select-none grow min-w-0">
 					<div className="flex items-center select-none grow min-w-0">
-						<div className="whitespace-nowrap overflow-hidden text-ellipsis grow min-w-0">
+						<div className="grow min-w-0">
 							{isTaskExpanded && <span className="font-bold">{t("chat:task.title")}</span>}
 							{isTaskExpanded && <span className="font-bold">{t("chat:task.title")}</span>}
 							{!isTaskExpanded && (
 							{!isTaskExpanded && (
-								<div>
-									<span className="font-bold mr-1">{t("chat:task.title")}</span>
-									<Mention text={task.text} />
+								<div className="flex items-center gap-2">
+									<SquarePen className="size-3 shrink-0" />
+									<span className="whitespace-nowrap overflow-hidden text-ellipsis">
+										<Mention text={task.text} />
+									</span>
 								</div>
 								</div>
 							)}
 							)}
 						</div>
 						</div>
@@ -156,7 +170,11 @@ const TaskHeader = ({
 								<button
 								<button
 									onClick={() => setIsTaskExpanded(!isTaskExpanded)}
 									onClick={() => setIsTaskExpanded(!isTaskExpanded)}
 									className="shrink-0 min-h-[20px] min-w-[20px] p-[2px] cursor-pointer opacity-85 hover:opacity-100 bg-transparent border-none rounded-md">
 									className="shrink-0 min-h-[20px] min-w-[20px] p-[2px] cursor-pointer opacity-85 hover:opacity-100 bg-transparent border-none rounded-md">
-									{isTaskExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
+									{isTaskExpanded ? (
+										<ChevronUp size={16} />
+									) : (
+										<ChevronDown size={16} className="opacity-0 group-hover:opacity-100" />
+									)}
 								</button>
 								</button>
 							</StandardTooltip>
 							</StandardTooltip>
 						</div>
 						</div>
@@ -166,6 +184,7 @@ const TaskHeader = ({
 					<div
 					<div
 						className="flex items-center gap-2 text-sm text-muted-foreground/70"
 						className="flex items-center gap-2 text-sm text-muted-foreground/70"
 						onClick={(e) => e.stopPropagation()}>
 						onClick={(e) => e.stopPropagation()}>
+						<Coins className="size-3 shrink-0" />
 						<StandardTooltip
 						<StandardTooltip
 							content={
 							content={
 								<div className="space-y-1">
 								<div className="space-y-1">
@@ -220,7 +239,7 @@ const TaskHeader = ({
 							className="text-vscode-font-size overflow-y-auto break-words break-anywhere relative">
 							className="text-vscode-font-size overflow-y-auto break-words break-anywhere relative">
 							<div
 							<div
 								ref={textRef}
 								ref={textRef}
-								className="overflow-auto max-h-80 whitespace-pre-wrap break-words break-anywhere cursor-text"
+								className="overflow-auto max-h-80 whitespace-pre-wrap break-words break-anywhere cursor-text py-0.5"
 								style={{
 								style={{
 									display: "-webkit-box",
 									display: "-webkit-box",
 									WebkitLineClamp: "unset",
 									WebkitLineClamp: "unset",
@@ -231,18 +250,22 @@ const TaskHeader = ({
 						</div>
 						</div>
 						{task.images && task.images.length > 0 && <Thumbnails images={task.images} />}
 						{task.images && task.images.length > 0 && <Thumbnails images={task.images} />}
 
 
-						<div className="border-t border-b border-vscode-panel-border/50 py-4 mt-2 mb-1">
-							<table className="w-full">
+						<div onClick={(e) => e.stopPropagation()}>
+							<TaskActions item={currentTaskItem} buttonsDisabled={buttonsDisabled} />
+						</div>
+
+						<div className="pt-3 mt-2 -mx-2.5 px-2.5 border-t border-vscode-sideBar-background">
+							<table className="w-full text-sm">
 								<tbody>
 								<tbody>
 									{contextWindow > 0 && (
 									{contextWindow > 0 && (
 										<tr>
 										<tr>
 											<th
 											<th
-												className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]"
+												className="font-medium text-left align-top w-1 whitespace-nowrap pr-3 h-[24px]"
 												data-testid="context-window-label">
 												data-testid="context-window-label">
 												{t("chat:task.contextWindow")}
 												{t("chat:task.contextWindow")}
 											</th>
 											</th>
-											<td className="align-top">
-												<div className={`max-w-80 -mt-0.5 flex flex-nowrap gap-1`}>
+											<td className="font-light align-top">
+									<div className={`max-w-md -mt-1.5 flex flex-nowrap gap-1`}>
 													<ContextWindowProgress
 													<ContextWindowProgress
 														contextWindow={contextWindow}
 														contextWindow={contextWindow}
 														contextTokens={contextTokens || 0}
 														contextTokens={contextTokens || 0}
@@ -263,10 +286,10 @@ const TaskHeader = ({
 									)}
 									)}
 
 
 									<tr>
 									<tr>
-										<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
+										<th className="font-medium text-left align-top w-1 whitespace-nowrap pr-3 h-[24px]">
 											{t("chat:task.tokens")}
 											{t("chat:task.tokens")}
 										</th>
 										</th>
-										<td className="align-top">
+										<td className="font-light align-top">
 											<div className="flex items-center gap-1 flex-wrap">
 											<div className="flex items-center gap-1 flex-wrap">
 												{typeof tokensIn === "number" && tokensIn > 0 && (
 												{typeof tokensIn === "number" && tokensIn > 0 && (
 													<span>↑ {formatLargeNumber(tokensIn)}</span>
 													<span>↑ {formatLargeNumber(tokensIn)}</span>
@@ -281,16 +304,22 @@ const TaskHeader = ({
 									{((typeof cacheReads === "number" && cacheReads > 0) ||
 									{((typeof cacheReads === "number" && cacheReads > 0) ||
 										(typeof cacheWrites === "number" && cacheWrites > 0)) && (
 										(typeof cacheWrites === "number" && cacheWrites > 0)) && (
 										<tr>
 										<tr>
-											<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
+											<th className="font-medium text-left align-top w-1 whitespace-nowrap pr-3 h-[24px]">
 												{t("chat:task.cache")}
 												{t("chat:task.cache")}
 											</th>
 											</th>
-											<td className="align-top">
+											<td className="font-light align-top">
 												<div className="flex items-center gap-1 flex-wrap">
 												<div className="flex items-center gap-1 flex-wrap">
 													{typeof cacheWrites === "number" && cacheWrites > 0 && (
 													{typeof cacheWrites === "number" && cacheWrites > 0 && (
-														<span>↑ {formatLargeNumber(cacheWrites)}</span>
+														<>
+															<HardDriveDownload className="size-2.5" />
+															<span>{formatLargeNumber(cacheWrites)}</span>
+														</>
 													)}
 													)}
 													{typeof cacheReads === "number" && cacheReads > 0 && (
 													{typeof cacheReads === "number" && cacheReads > 0 && (
-														<span>↓ {formatLargeNumber(cacheReads)}</span>
+														<>
+															<HardDriveUpload className="size-2.5" />
+															<span>{formatLargeNumber(cacheReads)}</span>
+														</>
 													)}
 													)}
 												</div>
 												</div>
 											</td>
 											</td>
@@ -299,10 +328,10 @@ const TaskHeader = ({
 
 
 									{!!totalCost && (
 									{!!totalCost && (
 										<tr>
 										<tr>
-											<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
+											<th className="font-medium text-left align-top w-1 whitespace-nowrap pr-3 h-[24px]">
 												{t("chat:task.apiCost")}
 												{t("chat:task.apiCost")}
 											</th>
 											</th>
-											<td className="align-top">
+											<td className="font-light align-top">
 												<span>${totalCost?.toFixed(2)}</span>
 												<span>${totalCost?.toFixed(2)}</span>
 											</td>
 											</td>
 										</tr>
 										</tr>
@@ -311,24 +340,22 @@ const TaskHeader = ({
 									{/* Size display */}
 									{/* Size display */}
 									{!!currentTaskItem?.size && currentTaskItem.size > 0 && (
 									{!!currentTaskItem?.size && currentTaskItem.size > 0 && (
 										<tr>
 										<tr>
-											<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-2  h-[20px]">
+											<th className="font-medium text-left align-top w-1 whitespace-nowrap pr-2 h-[20px]">
 												{t("chat:task.size")}
 												{t("chat:task.size")}
 											</th>
 											</th>
-											<td className="align-top">{prettyBytes(currentTaskItem.size)}</td>
+											<td className="font-light align-top">
+												{prettyBytes(currentTaskItem.size)}
+											</td>
 										</tr>
 										</tr>
 									)}
 									)}
 								</tbody>
 								</tbody>
 							</table>
 							</table>
 						</div>
 						</div>
-
-						{/* Footer with task management buttons */}
-						<div onClick={(e) => e.stopPropagation()}>
-							<TaskActions item={currentTaskItem} buttonsDisabled={buttonsDisabled} />
-						</div>
 					</>
 					</>
 				)}
 				)}
+				{/* Todo list - always shown at bottom when todos exist */}
+				{hasTodos && <TodoListDisplay todos={todos ?? (task as any)?.tool?.todos ?? []} />}
 			</div>
 			</div>
-			<TodoListDisplay todos={todos ?? (task as any)?.tool?.todos ?? []} />
 			<CloudUpsellDialog open={isOpen} onOpenChange={closeUpsell} onConnect={handleConnect} />
 			<CloudUpsellDialog open={isOpen} onOpenChange={closeUpsell} onConnect={handleConnect} />
 		</div>
 		</div>
 	)
 	)

+ 131 - 0
webview-ui/src/components/chat/TodoChangeDisplay.tsx

@@ -0,0 +1,131 @@
+import { t } from "i18next"
+import { ArrowRight, Check, ListChecks, SquareDashed } from "lucide-react"
+
+type TodoStatus = "completed" | "in_progress" | "pending"
+
+interface TodoItem {
+	id?: string
+	content: string
+	status?: TodoStatus | string
+}
+
+interface TodoChangeDisplayProps {
+	previousTodos: TodoItem[]
+	newTodos: TodoItem[]
+}
+
+interface TodoGroup {
+	todos: TodoItem[]
+	status: TodoStatus | null
+	keyPrefix: string
+	className?: string
+}
+
+function getTodoIcon(status: TodoStatus | null) {
+	switch (status) {
+		case "completed":
+			return <Check className="size-3 mt-1 shrink-0" />
+		case "in_progress":
+			return <ArrowRight className="size-3 mt-1 shrink-0" />
+		default:
+			return <SquareDashed className="size-3 mt-1 shrink-0" />
+	}
+}
+
+function TodoList({ todos, status, keyPrefix, className }: TodoGroup) {
+	if (todos.length === 0) return null
+
+	return (
+		<ul className="list-none space-y-1 my-1">
+			{todos.map((todo) => {
+				const icon = getTodoIcon(status)
+				return (
+					<li
+						key={`${keyPrefix}-${todo.id || todo.content}`}
+						className={`flex flex-row gap-2 items-start ${className || ""}`}>
+						{icon}
+						<span>{todo.content}</span>
+					</li>
+				)
+			})}
+		</ul>
+	)
+}
+
+export function TodoChangeDisplay({ previousTodos, newTodos }: TodoChangeDisplayProps) {
+	const isInitialState = previousTodos.length === 0
+
+	// Determine which todos to display
+	let todoGroups: TodoGroup[]
+
+	if (isInitialState && newTodos.length > 0) {
+		// For initial state, show all todos grouped by status
+		todoGroups = [
+			{
+				todos: newTodos.filter((todo) => !todo.status || todo.status === "pending"),
+				status: null,
+				keyPrefix: "pending",
+			},
+			{
+				todos: newTodos.filter((todo) => todo.status === "in_progress"),
+				status: "in_progress",
+				keyPrefix: "in-progress",
+				className: "text-vscode-charts-yellow",
+			},
+			{
+				todos: newTodos.filter((todo) => todo.status === "completed"),
+				status: "completed",
+				keyPrefix: "completed",
+			},
+		]
+	} else {
+		// For updates, only show changes
+		const completedTodos = newTodos.filter((newTodo) => {
+			if (newTodo.status !== "completed") return false
+			const previousTodo = previousTodos.find((p) => p.id === newTodo.id || p.content === newTodo.content)
+			return !previousTodo || previousTodo.status !== "completed"
+		})
+
+		const startedTodos = newTodos.filter((newTodo) => {
+			if (newTodo.status !== "in_progress") return false
+			const previousTodo = previousTodos.find((p) => p.id === newTodo.id || p.content === newTodo.content)
+			return !previousTodo || previousTodo.status !== "in_progress"
+		})
+
+		todoGroups = [
+			{
+				todos: completedTodos,
+				status: "completed",
+				keyPrefix: "completed",
+			},
+			{
+				todos: startedTodos,
+				status: "in_progress",
+				keyPrefix: "started",
+				className: "text-vscode-charts-yellow",
+			},
+		]
+	}
+
+	// If no todos to display, don't render anything
+	if (todoGroups.every((group) => group.todos.length === 0)) {
+		return null
+	}
+
+	return (
+		<div data-todo-changes className="overflow-hidden">
+			<div className="flex items-center gap-2">
+				<ListChecks className="size-4 shrink-0" />
+				<span className="flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-base font-semibold">
+					{t("chat:todo.updated")}
+				</span>
+			</div>
+
+			<div className="pl-1 pr-1 pt-1 font-light leading-normal">
+				{todoGroups.map((group, index) => (
+					<TodoList key={index} {...group} />
+				))}
+			</div>
+		</div>
+	)
+}

+ 53 - 304
webview-ui/src/components/chat/TodoListDisplay.tsx

@@ -1,5 +1,21 @@
+import { cn } from "@/lib/utils"
+import { t } from "i18next"
+import { ArrowRight, Check, ListChecks, SquareDashed } from "lucide-react"
 import { useState, useRef, useMemo, useEffect } from "react"
 import { useState, useRef, useMemo, useEffect } from "react"
 
 
+type TodoStatus = "completed" | "in_progress" | "pending"
+
+function getTodoIcon(status: TodoStatus | null) {
+	switch (status) {
+		case "completed":
+			return <Check className={`size-3 mt-1 shrink-0`} />
+		case "in_progress":
+			return <ArrowRight className="size-3 mt-1 shrink-0" />
+		default:
+			return <SquareDashed className="size-3 mt-1 shrink-0" />
+	}
+}
+
 export function TodoListDisplay({ todos }: { todos: any[] }) {
 export function TodoListDisplay({ todos }: { todos: any[] }) {
 	const [isCollapsed, setIsCollapsed] = useState(true)
 	const [isCollapsed, setIsCollapsed] = useState(true)
 	const ulRef = useRef<HTMLUListElement>(null)
 	const ulRef = useRef<HTMLUListElement>(null)
@@ -37,317 +53,50 @@ export function TodoListDisplay({ todos }: { todos: any[] }) {
 
 
 	const allCompleted = completedCount === totalCount && totalCount > 0
 	const allCompleted = completedCount === totalCount && totalCount > 0
 
 
-	// Create the status icon for the most important todo
-	const getMostImportantTodoIcon = () => {
-		if (allCompleted) {
-			return (
-				<span
-					style={{
-						display: "inline-block",
-						width: 8,
-						height: 8,
-						borderRadius: "50%",
-						background: "var(--vscode-charts-green)",
-						marginRight: 8,
-						marginLeft: 2,
-						flexShrink: 0,
-					}}
-				/>
-			)
-		}
-
-		if (!mostImportantTodo) {
-			return (
-				<span
-					className="codicon codicon-checklist"
-					style={{
-						color: "var(--vscode-foreground)",
-						marginRight: 8,
-						marginLeft: 2,
-						flexShrink: 0,
-						fontSize: 14,
-					}}
-				/>
-			)
-		}
-
-		if (mostImportantTodo.status === "completed") {
-			return (
-				<span
-					style={{
-						display: "inline-block",
-						width: 8,
-						height: 8,
-						borderRadius: "50%",
-						background: "var(--vscode-charts-green)",
-						marginRight: 8,
-						marginLeft: 2,
-						flexShrink: 0,
-					}}
-				/>
-			)
-		}
-
-		if (mostImportantTodo.status === "in_progress") {
-			return (
-				<span
-					style={{
-						display: "inline-block",
-						width: 8,
-						height: 8,
-						borderRadius: "50%",
-						background: "var(--vscode-charts-yellow)",
-						marginRight: 8,
-						marginLeft: 2,
-						flexShrink: 0,
-					}}
-				/>
-			)
-		}
-
-		// Default not-started todo
-		return (
-			<span
-				style={{
-					display: "inline-block",
-					width: 8,
-					height: 8,
-					borderRadius: "50%",
-					border: "1px solid var(--vscode-descriptionForeground)",
-					background: "transparent",
-					marginRight: 8,
-					marginLeft: 2,
-					flexShrink: 0,
-				}}
-			/>
-		)
-	}
-
 	return (
 	return (
-		<div
-			className="border border-t-0 rounded-b-xs relative"
-			style={{
-				margin: "0",
-				padding: "6px 10px",
-				background: "var(--vscode-editor-background,transparent)",
-				borderColor: "var(--vscode-panel-border)",
-			}}>
+		<div data-todo-list className="mt-1 -mx-2.5 border-t border-vscode-sideBar-background overflow-hidden">
 			<div
 			<div
-				style={{
-					display: "flex",
-					alignItems: "center",
-					gap: 2,
-					marginBottom: 0,
-					cursor: "pointer",
-					userSelect: "none",
-				}}
+				className={cn(
+					"flex items-center gap-2 pt-2 px-2.5 cursor-pointer select-none",
+					mostImportantTodo?.status === "in_progress" && isCollapsed
+						? "text-vscode-charts-yellow"
+						: "text-vscode-foreground",
+				)}
 				onClick={() => setIsCollapsed((v) => !v)}>
 				onClick={() => setIsCollapsed((v) => !v)}>
-				{getMostImportantTodoIcon()}
-				<span
-					style={{
-						fontWeight: 500,
-						color: allCompleted
-							? "var(--vscode-charts-green)"
-							: mostImportantTodo?.status === "in_progress"
-								? "var(--vscode-charts-yellow)"
-								: "var(--vscode-foreground)",
-						flex: 1,
-						overflow: "hidden",
-						textOverflow: "ellipsis",
-						whiteSpace: "nowrap",
-					}}>
-					{allCompleted ? "All tasks completed!" : mostImportantTodo?.content || "No pending tasks"}
+				<ListChecks className="size-3 shrink-0" />
+				<span className="flex-1 overflow-hidden text-ellipsis whitespace-nowrap">
+					{isCollapsed
+						? allCompleted
+							? t("chat:todo.complete", { total: completedCount })
+							: mostImportantTodo?.content // show current todo while not done
+						: t("chat:todo.partial", { completed: completedCount, total: totalCount })}
 				</span>
 				</span>
-				<div style={{ display: "flex", alignItems: "center", gap: 4, flexShrink: 0 }}>
-					<span
-						className="codicon codicon-checklist"
-						style={{
-							color: "var(--vscode-descriptionForeground)",
-							fontSize: 12,
-						}}
-					/>
-					<span
-						style={{
-							color: "var(--vscode-descriptionForeground)",
-							fontSize: 12,
-							fontWeight: 500,
-						}}>
+				{isCollapsed && completedCount < totalCount && (
+					<div className="shrink-0 text-vscode-descriptionForeground text-xs">
 						{completedCount}/{totalCount}
 						{completedCount}/{totalCount}
-					</span>
-				</div>
+					</div>
+				)}
 			</div>
 			</div>
-			{/* Floating panel for expanded state */}
+			{/* Inline expanded list */}
 			{!isCollapsed && (
 			{!isCollapsed && (
-				<>
-					{/* Backdrop */}
-					<div
-						style={{
-							position: "fixed",
-							top: 0,
-							left: 0,
-							right: 0,
-							bottom: 0,
-							background: "rgba(0, 0, 0, 0.1)",
-							zIndex: 1000,
-						}}
-						onClick={() => setIsCollapsed(true)}
-					/>
-					{/* Floating panel */}
-					<div
-						style={{
-							position: "absolute",
-							top: "100%",
-							left: 0,
-							right: 0,
-							marginTop: 4,
-							background: "var(--vscode-editor-background)",
-							border: "1px solid var(--vscode-panel-border)",
-							borderRadius: 6,
-							boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
-							zIndex: 1001,
-							maxHeight: "400px",
-							minHeight: "200px",
-							overflow: "hidden",
-						}}>
-						{/* Panel header */}
-						<div
-							style={{
-								display: "flex",
-								alignItems: "center",
-								justifyContent: "space-between",
-								padding: "12px 16px",
-								borderBottom: "1px solid var(--vscode-panel-border)",
-								background: "var(--vscode-editor-background)",
-							}}>
-							<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
-								<span
-									className="codicon codicon-checklist"
-									style={{ color: "var(--vscode-foreground)" }}
-								/>
-								<span style={{ fontWeight: "bold", fontSize: 14 }}>Todo List</span>
-								<span
-									style={{
-										color: "var(--vscode-descriptionForeground)",
-										fontSize: 13,
-										fontWeight: 500,
-									}}>
-									{completedCount}/{totalCount}
-								</span>
-							</div>
-							<span
-								className="codicon codicon-chevron-up"
-								style={{
-									fontSize: 14,
-									opacity: 0.7,
-									cursor: "pointer",
-									padding: "4px",
-									borderRadius: "2px",
-								}}
-								onClick={(e) => {
-									e.stopPropagation()
-									setIsCollapsed(true)
-								}}
-								onMouseEnter={(e) => {
-									e.currentTarget.style.opacity = "1"
-									e.currentTarget.style.background = "var(--vscode-toolbar-hoverBackground)"
-								}}
-								onMouseLeave={(e) => {
-									e.currentTarget.style.opacity = "0.7"
-									e.currentTarget.style.background = "transparent"
-								}}
-							/>
-						</div>
-						{/* Todo list */}
-						<ul
-							ref={ulRef}
-							style={{
-								margin: 0,
-								paddingLeft: 0,
-								listStyle: "none",
-								maxHeight: "340px",
-								overflowY: "auto",
-								padding: "12px 16px",
-							}}>
-							{todos.map((todo: any, idx: number) => {
-								let icon
-								if (todo.status === "completed") {
-									icon = (
-										<span
-											style={{
-												display: "inline-block",
-												width: 8,
-												height: 8,
-												borderRadius: "50%",
-												background: "var(--vscode-charts-green)",
-												marginRight: 8,
-												marginTop: 7,
-												flexShrink: 0,
-											}}
-										/>
-									)
-								} else if (todo.status === "in_progress") {
-									icon = (
-										<span
-											style={{
-												display: "inline-block",
-												width: 8,
-												height: 8,
-												borderRadius: "50%",
-												background: "var(--vscode-charts-yellow)",
-												marginRight: 8,
-												marginTop: 7,
-												flexShrink: 0,
-											}}
-										/>
-									)
-								} else {
-									icon = (
-										<span
-											style={{
-												display: "inline-block",
-												width: 8,
-												height: 8,
-												borderRadius: "50%",
-												border: "1px solid var(--vscode-descriptionForeground)",
-												background: "transparent",
-												marginRight: 8,
-												marginTop: 7,
-												flexShrink: 0,
-											}}
-										/>
-									)
-								}
-								return (
-									<li
-										key={todo.id || todo.content}
-										ref={(el) => (itemRefs.current[idx] = el)}
-										style={{
-											marginBottom: 8,
-											display: "flex",
-											alignItems: "flex-start",
-											minHeight: 20,
-											lineHeight: "1.4",
-										}}>
-										{icon}
-										<span
-											style={{
-												fontWeight: 500,
-												color:
-													todo.status === "completed"
-														? "var(--vscode-charts-green)"
-														: todo.status === "in_progress"
-															? "var(--vscode-charts-yellow)"
-															: "var(--vscode-foreground)",
-												wordBreak: "break-word",
-											}}>
-											{todo.content}
-										</span>
-									</li>
-								)
-							})}
-						</ul>
-					</div>
-				</>
+				<ul ref={ulRef} className="list-none max-h-[300px] overflow-y-auto mt-2 -mb-1 pb-0 px-2 cursor-default">
+					{todos.map((todo: any, idx: number) => {
+						const icon = getTodoIcon(todo.status as TodoStatus)
+						return (
+							<li
+								key={todo.id || todo.content}
+								ref={(el) => (itemRefs.current[idx] = el)}
+								className={cn(
+									"font-light flex flex-row gap-2 items-start min-h-[20px] leading-normal mb-2",
+									todo.status === "in_progress" && "text-vscode-charts-yellow",
+									todo.status !== "in_progress" && todo.status !== "completed" && "opacity-60",
+								)}>
+								{icon}
+								<span>{todo.content}</span>
+							</li>
+						)
+					})}
+				</ul>
 			)}
 			)}
 		</div>
 		</div>
 	)
 	)

+ 8 - 1
webview-ui/src/i18n/locales/ca/chat.json

@@ -423,5 +423,12 @@
 		"terminal": "Terminal",
 		"terminal": "Terminal",
 		"url": "Enganxa la URL per obtenir-ne el contingut"
 		"url": "Enganxa la URL per obtenir-ne el contingut"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{completed}} de {{total}} tasques pendents fetes",
+		"complete": "{{total}} tasques pendents fetes",
+		"updated": "S'ha actualitzat la llista de tasques pendents",
+		"completed": "Completat",
+		"started": "Iniciat"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/de/chat.json

@@ -423,5 +423,12 @@
 		"wantsToRun": "Roo möchte einen Slash-Befehl ausführen",
 		"wantsToRun": "Roo möchte einen Slash-Befehl ausführen",
 		"didRun": "Roo hat einen Slash-Befehl ausgeführt"
 		"didRun": "Roo hat einen Slash-Befehl ausgeführt"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{completed}} von {{total}} To-Dos erledigt",
+		"complete": "{{total}} To-Dos erledigt",
+		"updated": "Die To-Do-Liste wurde aktualisiert",
+		"completed": "Abgeschlossen",
+		"started": "Gestartet"
+	}
 }
 }

+ 7 - 0
webview-ui/src/i18n/locales/en/chat.json

@@ -414,5 +414,12 @@
 		"problems": "Problems",
 		"problems": "Problems",
 		"terminal": "Terminal",
 		"terminal": "Terminal",
 		"url": "Paste URL to fetch contents"
 		"url": "Paste URL to fetch contents"
+	},
+	"todo": {
+		"partial": "{{completed}} of {{total}} to-dos done",
+		"complete": "{{total}} to-dos done",
+		"updated": "Updated the to-do list",
+		"completed": "Completed",
+		"started": "Started"
 	}
 	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/es/chat.json

@@ -423,5 +423,12 @@
 		"wantsToRun": "Roo quiere ejecutar un comando slash",
 		"wantsToRun": "Roo quiere ejecutar un comando slash",
 		"didRun": "Roo ejecutó un comando slash"
 		"didRun": "Roo ejecutó un comando slash"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{completed}} de {{total}} tareas pendientes realizadas",
+		"complete": "{{total}} tareas pendientes realizadas",
+		"updated": "Se actualizó la lista de tareas pendientes",
+		"completed": "Completado",
+		"started": "Iniciado"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/fr/chat.json

@@ -423,5 +423,12 @@
 		"wantsToRun": "Roo veut exécuter une commande slash",
 		"wantsToRun": "Roo veut exécuter une commande slash",
 		"didRun": "Roo a exécuté une commande slash"
 		"didRun": "Roo a exécuté une commande slash"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{completed}} sur {{total}} tâches terminées",
+		"complete": "{{total}} tâches terminées",
+		"updated": "La liste des tâches a été mise à jour",
+		"completed": "Terminé",
+		"started": "Commencé"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/hi/chat.json

@@ -423,5 +423,12 @@
 		"wantsToRun": "Roo एक स्लैश कमांड चलाना चाहता है",
 		"wantsToRun": "Roo एक स्लैश कमांड चलाना चाहता है",
 		"didRun": "Roo ने एक स्लैश कमांड चलाया"
 		"didRun": "Roo ने एक स्लैश कमांड चलाया"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{total}} में से {{completed}} टू-डू हो गए",
+		"complete": "{{total}} टू-डू हो गए",
+		"updated": "टू-डू सूची अपडेट की गई",
+		"completed": "पूरा हुआ",
+		"started": "शुरू हुआ"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/id/chat.json

@@ -429,5 +429,12 @@
 		"wantsToRun": "Roo ingin menjalankan perintah slash",
 		"wantsToRun": "Roo ingin menjalankan perintah slash",
 		"didRun": "Roo telah menjalankan perintah slash"
 		"didRun": "Roo telah menjalankan perintah slash"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{completed}} dari {{total}} to-do selesai",
+		"complete": "{{total}} to-do selesai",
+		"updated": "Memperbarui daftar to-do",
+		"completed": "Selesai",
+		"started": "Dimulai"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/it/chat.json

@@ -423,5 +423,12 @@
 		"wantsToRun": "Roo vuole eseguire un comando slash",
 		"wantsToRun": "Roo vuole eseguire un comando slash",
 		"didRun": "Roo ha eseguito un comando slash"
 		"didRun": "Roo ha eseguito un comando slash"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{completed}} di {{total}} cose da fare completate",
+		"complete": "{{total}} cose da fare completate",
+		"updated": "Aggiornata la lista delle cose da fare",
+		"completed": "Completato",
+		"started": "Iniziato"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/ja/chat.json

@@ -423,5 +423,12 @@
 		"wantsToRun": "Rooはスラッシュコマンドを実行したい",
 		"wantsToRun": "Rooはスラッシュコマンドを実行したい",
 		"didRun": "Rooはスラッシュコマンドを実行しました"
 		"didRun": "Rooはスラッシュコマンドを実行しました"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{total}}件中{{completed}}件のTo-Doが完了",
+		"complete": "{{total}}件のTo-Doが完了",
+		"updated": "To-Doリストを更新しました",
+		"completed": "完了",
+		"started": "開始"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/ko/chat.json

@@ -423,5 +423,12 @@
 		"wantsToRun": "Roo가 슬래시 명령어를 실행하려고 합니다",
 		"wantsToRun": "Roo가 슬래시 명령어를 실행하려고 합니다",
 		"didRun": "Roo가 슬래시 명령어를 실행했습니다"
 		"didRun": "Roo가 슬래시 명령어를 실행했습니다"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{total}}개의 할 일 중 {{completed}}개 완료",
+		"complete": "{{total}}개의 할 일 완료",
+		"updated": "할 일 목록을 업데이트했습니다",
+		"completed": "완료됨",
+		"started": "시작됨"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/nl/chat.json

@@ -423,5 +423,12 @@
 		"wantsToRun": "Roo wil een slash commando uitvoeren",
 		"wantsToRun": "Roo wil een slash commando uitvoeren",
 		"didRun": "Roo heeft een slash commando uitgevoerd"
 		"didRun": "Roo heeft een slash commando uitgevoerd"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{completed}} van {{total}} to-do's voltooid",
+		"complete": "{{total}} to-do's voltooid",
+		"updated": "De to-do-lijst is bijgewerkt",
+		"completed": "Voltooid",
+		"started": "Gestart"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/pl/chat.json

@@ -423,5 +423,12 @@
 		"wantsToRun": "Roo chce uruchomić komendę slash",
 		"wantsToRun": "Roo chce uruchomić komendę slash",
 		"didRun": "Roo uruchomił komendę slash"
 		"didRun": "Roo uruchomił komendę slash"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "Ukończono {{completed}} z {{total}} zadań do wykonania",
+		"complete": "Ukończono {{total}} zadań do wykonania",
+		"updated": "Zaktualizowano listę zadań do wykonania",
+		"completed": "Ukończono",
+		"started": "Rozpoczęto"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/pt-BR/chat.json

@@ -423,5 +423,12 @@
 		"wantsToRun": "Roo quer executar um comando slash",
 		"wantsToRun": "Roo quer executar um comando slash",
 		"didRun": "Roo executou um comando slash"
 		"didRun": "Roo executou um comando slash"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{completed}} de {{total}} tarefas concluídas",
+		"complete": "{{total}} tarefas concluídas",
+		"updated": "A lista de tarefas foi atualizada",
+		"completed": "Concluído",
+		"started": "Iniciado"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/ru/chat.json

@@ -424,5 +424,12 @@
 		"wantsToRun": "Roo хочет выполнить слеш-команду",
 		"wantsToRun": "Roo хочет выполнить слеш-команду",
 		"didRun": "Roo выполнил слеш-команду"
 		"didRun": "Roo выполнил слеш-команду"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{completed}} из {{total}} задач выполнено",
+		"complete": "{{total}} задач выполнено",
+		"updated": "Список задач обновлен",
+		"completed": "Завершено",
+		"started": "Начато"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/tr/chat.json

@@ -424,5 +424,12 @@
 		"wantsToRun": "Roo bir slash komutu çalıştırmak istiyor",
 		"wantsToRun": "Roo bir slash komutu çalıştırmak istiyor",
 		"didRun": "Roo bir slash komutu çalıştırdı"
 		"didRun": "Roo bir slash komutu çalıştırdı"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{total}} yapılacaklar listesinden {{completed}} tanesi tamamlandı",
+		"complete": "{{total}} yapılacaklar listesi tamamlandı",
+		"updated": "Yapılacaklar listesi güncellendi",
+		"completed": "Tamamlandı",
+		"started": "Başladı"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/vi/chat.json

@@ -424,5 +424,12 @@
 		"wantsToRun": "Roo muốn chạy lệnh slash",
 		"wantsToRun": "Roo muốn chạy lệnh slash",
 		"didRun": "Roo đã chạy lệnh slash"
 		"didRun": "Roo đã chạy lệnh slash"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "{{completed}} trong tổng số {{total}} công việc đã hoàn thành",
+		"complete": "{{total}} công việc đã hoàn thành",
+		"updated": "Đã cập nhật danh sách công việc",
+		"completed": "Đã hoàn thành",
+		"started": "Đã bắt đầu"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/zh-CN/chat.json

@@ -424,5 +424,12 @@
 		"wantsToRun": "Roo 想要运行斜杠命令",
 		"wantsToRun": "Roo 想要运行斜杠命令",
 		"didRun": "Roo 运行了斜杠命令"
 		"didRun": "Roo 运行了斜杠命令"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "已完成 {{completed}} / {{total}} 个待办事项",
+		"complete": "已完成 {{total}} 个待办事项",
+		"updated": "已更新待办事项列表",
+		"completed": "已完成",
+		"started": "已开始"
+	}
 }
 }

+ 8 - 1
webview-ui/src/i18n/locales/zh-TW/chat.json

@@ -424,5 +424,12 @@
 		"wantsToRun": "Roo 想要執行斜線指令",
 		"wantsToRun": "Roo 想要執行斜線指令",
 		"didRun": "Roo 執行了斜線指令"
 		"didRun": "Roo 執行了斜線指令"
 	},
 	},
-	"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
+	"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
+	"todo": {
+		"partial": "已完成 {{completed}} / {{total}} 個待辦事項",
+		"complete": "已完成 {{total}} 個待辦事項",
+		"updated": "已更新待辦事項列表",
+		"completed": "已完成",
+		"started": "已開始"
+	}
 }
 }

+ 7 - 3
webview-ui/src/index.css

@@ -388,14 +388,18 @@ vscode-dropdown::part(listbox) {
 }
 }
 
 
 .mention-context-highlight {
 .mention-context-highlight {
-	background-color: color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
-	border-radius: 3px;
+	background-color: color-mix(in srgb, var(--vscode-badge-foreground) 15%, transparent);
+	border-radius: 6px;
+	padding: 1px 4px;
+	font-family: var(--font-mono);
 }
 }
 
 
 .mention-context-highlight-with-shadow {
 .mention-context-highlight-with-shadow {
-	background-color: color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
+	background-color: color-mix(in srgb, var(--vscode-badge-foreground) 15%, transparent);
 	border-radius: 3px;
 	border-radius: 3px;
+	padding: 1px 4px;
 	box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
 	box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
+	font-family: var(--font-mono);
 }
 }
 
 
 /**
 /**