From 788a21a192b190882762178a2695f9a1239a7c15 Mon Sep 17 00:00:00 2001 From: Toray Altas Date: Sun, 18 Jan 2026 03:20:07 -0500 Subject: [PATCH] feat(webview): add edit toggle for todo updates Extract SubtaskDetail into a shared type and remove the unused SubtaskCostList component. --- webview-ui/src/components/chat/ChatView.tsx | 14 +- .../src/components/chat/SubtaskCostList.tsx | 123 ------------------ webview-ui/src/components/chat/TaskHeader.tsx | 2 +- .../src/components/chat/TodoListDisplay.tsx | 2 +- .../chat/UpdateTodoListToolBlock.tsx | 22 ++++ .../chat/__tests__/TodoListDisplay.spec.tsx | 2 +- webview-ui/src/types/subtasks.ts | 13 ++ 7 files changed, 39 insertions(+), 139 deletions(-) delete mode 100644 webview-ui/src/components/chat/SubtaskCostList.tsx create mode 100644 webview-ui/src/types/subtasks.ts diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index ab232a072d..d222c8c504 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -48,19 +48,7 @@ import { QueuedMessages } from "./QueuedMessages" import DismissibleUpsell from "../common/DismissibleUpsell" import { useCloudUpsell } from "@src/hooks/useCloudUpsell" import { Cloud } from "lucide-react" - -/** - * Detailed information about a subtask for UI display. - * Matches the SubtaskDetail interface from backend aggregateTaskCosts.ts - */ -interface SubtaskDetail { - id: string // Task ID - name: string // First 50 chars of task description - tokens: number // tokensIn + tokensOut - cost: number // Aggregated total cost - status: "active" | "completed" | "delegated" - hasNestedChildren: boolean // Has its own subtasks -} +import type { SubtaskDetail } from "@src/types/subtasks" export interface ChatViewProps { isHidden: boolean diff --git a/webview-ui/src/components/chat/SubtaskCostList.tsx b/webview-ui/src/components/chat/SubtaskCostList.tsx deleted file mode 100644 index 7062f1b36a..0000000000 --- a/webview-ui/src/components/chat/SubtaskCostList.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { memo, useState } from "react" -import { useTranslation } from "react-i18next" -import { ChevronRight, ChevronDown } from "lucide-react" - -import { cn } from "@/lib/utils" -import { formatLargeNumber } from "@/utils/format" - -export interface SubtaskDetail { - id: string - name: string - tokens: number - cost: number - status: "active" | "completed" | "delegated" - hasNestedChildren: boolean -} - -export interface SubtaskCostListProps { - subtasks: SubtaskDetail[] - onSubtaskClick?: (subtaskId: string) => void -} - -interface SubtaskRowProps { - subtask: SubtaskDetail - isLast: boolean - onClick?: () => void - t: (key: string, options?: Record) => string -} - -const statusColors: Record = { - active: "bg-vscode-testing-iconQueued", - completed: "bg-vscode-testing-iconPassed", - delegated: "bg-vscode-testing-iconSkipped", -} - -const SubtaskRow = memo(({ subtask, isLast, onClick, t }: SubtaskRowProps) => { - return ( - - ) -}) - -SubtaskRow.displayName = "SubtaskRow" - -export const SubtaskCostList = memo(({ subtasks, onSubtaskClick }: SubtaskCostListProps) => { - const { t } = useTranslation("chat") - const [isExpanded, setIsExpanded] = useState(false) - - if (!subtasks || subtasks.length === 0) { - return null - } - - return ( -
- {/* Collapsible Header */} - - - {/* Expanded Subtask List */} - {isExpanded && ( -
- {subtasks.map((subtask, index) => ( - onSubtaskClick?.(subtask.id)} - t={t} - /> - ))} -
- )} -
- ) -}) - -SubtaskCostList.displayName = "SubtaskCostList" - -export default SubtaskCostList diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 56dfb77cd5..c6890e884e 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -35,7 +35,7 @@ import { ContextWindowProgress } from "./ContextWindowProgress" import { Mention } from "./Mention" import { TodoListDisplay } from "./TodoListDisplay" import { LucideIconButton } from "./LucideIconButton" -import type { SubtaskDetail } from "./SubtaskCostList" +import type { SubtaskDetail } from "@src/types/subtasks" export interface TaskHeaderProps { task: ClineMessage diff --git a/webview-ui/src/components/chat/TodoListDisplay.tsx b/webview-ui/src/components/chat/TodoListDisplay.tsx index 89eb1151e8..684ff82feb 100644 --- a/webview-ui/src/components/chat/TodoListDisplay.tsx +++ b/webview-ui/src/components/chat/TodoListDisplay.tsx @@ -5,7 +5,7 @@ import { useState, useRef, useMemo, useEffect } from "react" import { formatLargeNumber } from "@src/utils/format" -import type { SubtaskDetail } from "./SubtaskCostList" +import type { SubtaskDetail } from "@src/types/subtasks" type TodoStatus = "completed" | "in_progress" | "pending" diff --git a/webview-ui/src/components/chat/UpdateTodoListToolBlock.tsx b/webview-ui/src/components/chat/UpdateTodoListToolBlock.tsx index 310b8cd518..e851284505 100644 --- a/webview-ui/src/components/chat/UpdateTodoListToolBlock.tsx +++ b/webview-ui/src/components/chat/UpdateTodoListToolBlock.tsx @@ -179,6 +179,28 @@ const UpdateTodoListToolBlock: React.FC = ({ Todo List Updated
+ {editable && ( + + )}
diff --git a/webview-ui/src/components/chat/__tests__/TodoListDisplay.spec.tsx b/webview-ui/src/components/chat/__tests__/TodoListDisplay.spec.tsx index 74933bbc95..f5babe983c 100644 --- a/webview-ui/src/components/chat/__tests__/TodoListDisplay.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/TodoListDisplay.spec.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi } from "vitest" import { render, screen, fireEvent } from "@testing-library/react" import { TodoListDisplay } from "../TodoListDisplay" -import type { SubtaskDetail } from "../SubtaskCostList" +import type { SubtaskDetail } from "@src/types/subtasks" // Mock i18next vi.mock("i18next", () => ({ diff --git a/webview-ui/src/types/subtasks.ts b/webview-ui/src/types/subtasks.ts new file mode 100644 index 0000000000..a4cd97ed4f --- /dev/null +++ b/webview-ui/src/types/subtasks.ts @@ -0,0 +1,13 @@ +export type SubtaskDetail = { + /** Task ID */ + id: string + /** First 50 chars of task description */ + name: string + /** tokensIn + tokensOut */ + tokens: number + /** Aggregated total cost */ + cost: number + status: "active" | "completed" | "delegated" + /** Has its own subtasks */ + hasNestedChildren: boolean +}