From 685099e5449574f21af0c25d32f8127331299b0d Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Fri, 1 Aug 2025 17:14:24 +0100 Subject: [PATCH 01/10] Reorganizes the task header for cleanliness and in preparation for Cloud link --- webview-ui/src/components/chat/ChatView.tsx | 3 - .../src/components/chat/ShareButton.tsx | 5 +- webview-ui/src/components/chat/TaskHeader.tsx | 175 +++++++++++------- .../chat/__tests__/TaskHeader.spec.tsx | 1 - .../src/components/common/Thumbnails.tsx | 1 + 5 files changed, 108 insertions(+), 77 deletions(-) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 1fe93eb470..2aa71b9a01 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -805,8 +805,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction startNewTask(), [startNewTask]) - const { info: model } = useSelectedModel(apiConfiguration) const selectImages = useCallback(() => vscode.postMessage({ type: "selectImages" }), []) @@ -1765,7 +1763,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction diff --git a/webview-ui/src/components/chat/ShareButton.tsx b/webview-ui/src/components/chat/ShareButton.tsx index 48bb32ccff..f60e2a47e1 100644 --- a/webview-ui/src/components/chat/ShareButton.tsx +++ b/webview-ui/src/components/chat/ShareButton.tsx @@ -1,5 +1,6 @@ import { useState, useEffect, useRef } from "react" import { useTranslation } from "react-i18next" +import { Share } from "lucide-react" import type { HistoryItem, ShareVisibility } from "@roo-code/types" import { TelemetryEventName } from "@roo-code/types" @@ -159,7 +160,7 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => { disabled={disabled || shareButtonState.disabled} className="h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground" onClick={handleShareButtonClick}> - + @@ -221,7 +222,7 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => { disabled={disabled || shareButtonState.disabled} className="h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground" onClick={handleShareButtonClick}> - + )} diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 1896df486b..f6d65f0c3d 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -1,8 +1,8 @@ import { memo, useRef, useState } from "react" import { useWindowSize } from "react-use" import { useTranslation } from "react-i18next" -import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react" -import { CloudUpload, CloudDownload, FoldVertical } from "lucide-react" +import { CloudUpload, CloudDownload, FoldVertical, ChevronUp } from "lucide-react" +import prettyBytes from "pretty-bytes" import type { ClineMessage } from "@roo-code/types" @@ -10,7 +10,7 @@ import { getModelMaxOutputTokens } from "@roo/api" import { formatLargeNumber } from "@src/utils/format" import { cn } from "@src/lib/utils" -import { Button, StandardTooltip } from "@src/components/ui" +import { StandardTooltip } from "@src/components/ui" import { useExtensionState } from "@src/context/ExtensionStateContext" import { useSelectedModel } from "@/components/ui/hooks/useSelectedModel" @@ -32,7 +32,6 @@ export interface TaskHeaderProps { contextTokens: number buttonsDisabled: boolean handleCondenseContext: (taskId: string) => void - onClose: () => void todos?: any[] } @@ -46,7 +45,6 @@ const TaskHeader = ({ contextTokens, buttonsDisabled, handleCondenseContext, - onClose, todos, }: TaskHeaderProps) => { const { t } = useTranslation() @@ -74,55 +72,67 @@ const TaskHeader = ({ const hasTodos = todos && Array.isArray(todos) && todos.length > 0 return ( -
+
-
-
setIsTaskExpanded(!isTaskExpanded)}> -
- -
-
- - {t("chat:task.title")} - {!isTaskExpanded && ":"} - - {!isTaskExpanded && ( - - - - )} + )} + onClick={(e) => { + // Don't expand if clicking on buttons or interactive elements + if ( + e.target instanceof Element && + (e.target.closest("button") || + e.target.closest('[role="button"]') || + e.target.closest(".share-button") || + e.target.closest("[data-radix-popper-content-wrapper]") || + e.target.closest("img") || + e.target.tagName === "IMG") + ) { + return + } + + // Don't expand/collapse if user is selecting text + const selection = window.getSelection() + if (selection && selection.toString().length > 0) { + return + } + + setIsTaskExpanded(!isTaskExpanded) + }}> +
+
+
+ {isTaskExpanded && Task Details} + {!isTaskExpanded && }
+ {isTaskExpanded && ( +
e.stopPropagation()}> + + + +
+ )}
- - - + {!isTaskExpanded && ( +
e.stopPropagation()}> + +
+ )}
- {/* Collapsed state: Track context and cost if we have any */} {!isTaskExpanded && contextWindow > 0 && ( -
- - {condenseButton} - - {!!totalCost && ${totalCost.toFixed(2)}} +
e.stopPropagation()}> + + {formatLargeNumber(contextTokens || 0)} / {formatLargeNumber(contextWindow)} + + {!!totalCost && ${totalCost.toFixed(2)}}
)} {/* Expanded state: Show task text and images */} @@ -145,7 +155,7 @@ const TaskHeader = ({ {task.images && task.images.length > 0 && }
- {isTaskExpanded && contextWindow > 0 && ( + {contextWindow > 0 && (
@@ -169,23 +179,21 @@ const TaskHeader = ({ {condenseButton}
)} -
-
- {t("chat:task.tokens")} - {typeof tokensIn === "number" && tokensIn > 0 && ( - - - {formatLargeNumber(tokensIn)} - - )} - {typeof tokensOut === "number" && tokensOut > 0 && ( - - - {formatLargeNumber(tokensOut)} - - )} -
- {!totalCost && } + +
+ {t("chat:task.tokens")} + {typeof tokensIn === "number" && tokensIn > 0 && ( + + + {formatLargeNumber(tokensIn)} + + )} + {typeof tokensOut === "number" && tokensOut > 0 && ( + + + {formatLargeNumber(tokensOut)} + + )}
{((typeof cacheReads === "number" && cacheReads > 0) || @@ -208,14 +216,39 @@ const TaskHeader = ({ )} {!!totalCost && ( -
-
- {t("chat:task.apiCost")} - ${totalCost?.toFixed(2)} -
- +
+ {t("chat:task.apiCost")} + ${totalCost?.toFixed(2)} +
+ )} + + {/* Cache size display */} + {((typeof cacheReads === "number" && cacheReads > 0) || + (typeof cacheWrites === "number" && cacheWrites > 0)) && ( +
+ Cache size + + {prettyBytes(((cacheReads || 0) + (cacheWrites || 0)) * 4)} +
)} + + {/* Size display */} + {!!currentTaskItem?.size && currentTaskItem.size > 0 && ( +
+ Size + + {prettyBytes(currentTaskItem.size)} + +
+ )} +
+ + {/* Footer with task management buttons */} +
e.stopPropagation()}> +
)} diff --git a/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx b/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx index c04f7e45e5..d0bdb6f2de 100644 --- a/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx @@ -53,7 +53,6 @@ describe("TaskHeader", () => { contextTokens: 200, buttonsDisabled: false, handleCondenseContext: vi.fn(), - onClose: vi.fn(), } const queryClient = new QueryClient() diff --git a/webview-ui/src/components/common/Thumbnails.tsx b/webview-ui/src/components/common/Thumbnails.tsx index acdf5f4295..d0db36d561 100644 --- a/webview-ui/src/components/common/Thumbnails.tsx +++ b/webview-ui/src/components/common/Thumbnails.tsx @@ -39,6 +39,7 @@ const Thumbnails = ({ images, style, setImages, onHeightChange }: ThumbnailsProp return (
Date: Fri, 1 Aug 2025 17:56:44 +0100 Subject: [PATCH 02/10] More task ehader visual tweaks --- .../components/chat/ContextWindowProgress.tsx | 2 +- .../src/components/chat/ShareButton.tsx | 22 +- .../src/components/chat/TaskActions.tsx | 85 ++++---- webview-ui/src/components/chat/TaskHeader.tsx | 188 ++++++++++-------- 4 files changed, 166 insertions(+), 131 deletions(-) diff --git a/webview-ui/src/components/chat/ContextWindowProgress.tsx b/webview-ui/src/components/chat/ContextWindowProgress.tsx index 1ae80bb3db..6ddc23882a 100644 --- a/webview-ui/src/components/chat/ContextWindowProgress.tsx +++ b/webview-ui/src/components/chat/ContextWindowProgress.tsx @@ -55,7 +55,7 @@ export const ContextWindowProgress = ({ contextWindow, contextTokens, maxTokens return ( <> -
+
{formatLargeNumber(safeContextTokens)}
diff --git a/webview-ui/src/components/chat/ShareButton.tsx b/webview-ui/src/components/chat/ShareButton.tsx index f60e2a47e1..e77e08808e 100644 --- a/webview-ui/src/components/chat/ShareButton.tsx +++ b/webview-ui/src/components/chat/ShareButton.tsx @@ -27,9 +27,10 @@ import { interface ShareButtonProps { item?: HistoryItem disabled?: boolean + showLabel?: boolean } -export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => { +export const ShareButton = ({ item, disabled = false, showLabel = false }: ShareButtonProps) => { const [shareDropdownOpen, setShareDropdownOpen] = useState(false) const [connectModalOpen, setConnectModalOpen] = useState(false) const [shareSuccess, setShareSuccess] = useState<{ visibility: ShareVisibility; url: string } | null>(null) @@ -156,14 +157,20 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => { + {shareSuccess ? (
@@ -218,11 +225,16 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => { )} diff --git a/webview-ui/src/components/chat/TaskActions.tsx b/webview-ui/src/components/chat/TaskActions.tsx index 603b6be3e0..19014b0363 100644 --- a/webview-ui/src/components/chat/TaskActions.tsx +++ b/webview-ui/src/components/chat/TaskActions.tsx @@ -1,5 +1,4 @@ import { useState } from "react" -import prettyBytes from "pretty-bytes" import { useTranslation } from "react-i18next" import type { HistoryItem } from "@roo-code/types" @@ -22,48 +21,54 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => { const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard() return ( -
- - vscode.postMessage({ type: "exportCurrentTask" })} - /> - {item?.task && ( +
+ {/* Share button with label on the left */} +
+ +
+ + {/* Other action buttons on the right */} +
copyWithFeedback(item.task, e)} + iconClass="codicon-desktop-download" + title={t("chat:task.export")} + onClick={() => vscode.postMessage({ type: "exportCurrentTask" })} /> - )} - {!!item?.size && item.size > 0 && ( - <> -
- { - e.stopPropagation() + {item?.task && ( + copyWithFeedback(item.task, e)} + /> + )} + {!!item?.size && item.size > 0 && ( + <> +
+ { + e.stopPropagation() - if (e.shiftKey) { - vscode.postMessage({ type: "deleteTaskWithId", text: item.id }) - } else { - setDeleteTaskId(item.id) - } - }} - /> - {prettyBytes(item.size)} -
- {deleteTaskId && ( - !open && setDeleteTaskId(null)} - open - /> - )} - - )} + if (e.shiftKey) { + vscode.postMessage({ type: "deleteTaskWithId", text: item.id }) + } else { + setDeleteTaskId(item.id) + } + }} + /> +
+ {deleteTaskId && ( + !open && setDeleteTaskId(null)} + open + /> + )} + + )} +
) } diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index f6d65f0c3d..1c9c2031ae 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -154,100 +154,118 @@ const TaskHeader = ({
{task.images && task.images.length > 0 && } -
- {contextWindow > 0 && ( -
-
- - {t("chat:task.contextWindow")} - -
- - {condenseButton} -
- )} +
+ + + {contextWindow > 0 && ( + + + + + )} -
- {t("chat:task.tokens")} - {typeof tokensIn === "number" && tokensIn > 0 && ( - - - {formatLargeNumber(tokensIn)} - - )} - {typeof tokensOut === "number" && tokensOut > 0 && ( - - - {formatLargeNumber(tokensOut)} - - )} -
+ + + + - {((typeof cacheReads === "number" && cacheReads > 0) || - (typeof cacheWrites === "number" && cacheWrites > 0)) && ( -
- {t("chat:task.cache")} - {typeof cacheWrites === "number" && cacheWrites > 0 && ( - - - {formatLargeNumber(cacheWrites)} - + {((typeof cacheReads === "number" && cacheReads > 0) || + (typeof cacheWrites === "number" && cacheWrites > 0)) && ( +
+ + + )} - {typeof cacheReads === "number" && cacheReads > 0 && ( - - - {formatLargeNumber(cacheReads)} - - )} - - )} - {!!totalCost && ( -
- {t("chat:task.apiCost")} - ${totalCost?.toFixed(2)} -
- )} + {!!totalCost && ( +
+ + + + )} - {/* Cache size display */} - {((typeof cacheReads === "number" && cacheReads > 0) || - (typeof cacheWrites === "number" && cacheWrites > 0)) && ( -
- Cache size - - {prettyBytes(((cacheReads || 0) + (cacheWrites || 0)) * 4)} - -
- )} + {/* Cache size display */} + {((typeof cacheReads === "number" && cacheReads > 0) || + (typeof cacheWrites === "number" && cacheWrites > 0)) && ( +
+ + + + )} - {/* Size display */} - {!!currentTaskItem?.size && currentTaskItem.size > 0 && ( -
- Size - - {prettyBytes(currentTaskItem.size)} - -
- )} + {/* Size display */} + {!!currentTaskItem?.size && currentTaskItem.size > 0 && ( +
+ + + + )} + +
+ {t("chat:task.contextWindow")} + +
+ + {condenseButton} +
+
+ {t("chat:task.tokens")} + +
+ {typeof tokensIn === "number" && tokensIn > 0 && ( + ↑ {formatLargeNumber(tokensIn)} + )} + {typeof tokensOut === "number" && tokensOut > 0 && ( + ↓ {formatLargeNumber(tokensOut)} + )} +
+
+ {t("chat:task.cache")} + +
+ {typeof cacheWrites === "number" && cacheWrites > 0 && ( + + + {formatLargeNumber(cacheWrites)} + + )} + {typeof cacheReads === "number" && cacheReads > 0 && ( + + + {formatLargeNumber(cacheReads)} + + )} +
+
+ {t("chat:task.apiCost")} + + ${totalCost?.toFixed(2)} +
+ Cache size + + {prettyBytes(((cacheReads || 0) + (cacheWrites || 0)) * 4)} +
+ Size + {prettyBytes(currentTaskItem.size)}
{/* Footer with task management buttons */} -
e.stopPropagation()}> +
e.stopPropagation()}>
From b64f2aa71546d196a55a39bb23236c8ce08152b6 Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Fri, 1 Aug 2025 18:21:46 +0100 Subject: [PATCH 03/10] Translations for new task header --- webview-ui/src/components/chat/TaskHeader.tsx | 4 ++-- webview-ui/src/i18n/locales/ca/chat.json | 9 +++++---- webview-ui/src/i18n/locales/de/chat.json | 9 +++++---- webview-ui/src/i18n/locales/en/chat.json | 10 ++++++---- webview-ui/src/i18n/locales/es/chat.json | 9 +++++---- webview-ui/src/i18n/locales/fr/chat.json | 9 +++++---- webview-ui/src/i18n/locales/hi/chat.json | 9 +++++---- webview-ui/src/i18n/locales/id/chat.json | 9 +++++---- webview-ui/src/i18n/locales/it/chat.json | 9 +++++---- webview-ui/src/i18n/locales/ja/chat.json | 9 +++++---- webview-ui/src/i18n/locales/ko/chat.json | 9 +++++---- webview-ui/src/i18n/locales/nl/chat.json | 9 +++++---- webview-ui/src/i18n/locales/pl/chat.json | 9 +++++---- webview-ui/src/i18n/locales/pt-BR/chat.json | 9 +++++---- webview-ui/src/i18n/locales/ru/chat.json | 9 +++++---- webview-ui/src/i18n/locales/tr/chat.json | 9 +++++---- webview-ui/src/i18n/locales/vi/chat.json | 9 +++++---- webview-ui/src/i18n/locales/zh-CN/chat.json | 9 +++++---- webview-ui/src/i18n/locales/zh-TW/chat.json | 5 +++-- 19 files changed, 91 insertions(+), 72 deletions(-) diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 1c9c2031ae..2ed252187f 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -243,7 +243,7 @@ const TaskHeader = ({ (typeof cacheWrites === "number" && cacheWrites > 0)) && ( - Cache size + {t("chat:task.cache")} {prettyBytes(((cacheReads || 0) + (cacheWrites || 0)) * 4)} @@ -255,7 +255,7 @@ const TaskHeader = ({ {!!currentTaskItem?.size && currentTaskItem.size > 0 && ( - Size + {t("chat:task.size")} {prettyBytes(currentTaskItem.size)} diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index e83051c0dc..627a0b899f 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -4,10 +4,11 @@ "title": "Tasca", "seeMore": "Veure més", "seeLess": "Veure menys", - "tokens": "Tokens:", - "cache": "Caché:", - "apiCost": "Cost d'API:", - "contextWindow": "Finestra de context:", + "tokens": "Tokens", + "cache": "Caché", + "apiCost": "Cost d'API", + "size": "Mida", + "contextWindow": "Finestra de context", "closeAndStart": "Tancar tasca i iniciar-ne una de nova", "export": "Exportar historial de tasques", "delete": "Eliminar tasca (Shift + Clic per ometre confirmació)", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 345277eb7c..5ed175f1bb 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -4,10 +4,11 @@ "title": "Aufgabe", "seeMore": "Mehr anzeigen", "seeLess": "Weniger anzeigen", - "tokens": "Tokens:", - "cache": "Cache:", - "apiCost": "API-Kosten:", - "contextWindow": "Kontextfenster:", + "tokens": "Tokens", + "cache": "Cache", + "apiCost": "API-Kosten", + "size": "Größe", + "contextWindow": "Kontextfenster", "closeAndStart": "Aufgabe schließen und neue starten", "export": "Aufgabenverlauf exportieren", "delete": "Aufgabe löschen (Shift + Klick zum Überspringen der Bestätigung)", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 07bcd770d7..61999c4936 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -4,11 +4,13 @@ "title": "Task", "seeMore": "See more", "seeLess": "See less", - "tokens": "Tokens:", - "cache": "Cache:", - "apiCost": "API Cost:", + "tokens": "Tokens", + "cache": "Cache", + "cacheSize": "Cache size", + "apiCost": "API Cost", + "size": "Size", "condenseContext": "Intelligently condense context", - "contextWindow": "Context Length:", + "contextWindow": "Context Length", "closeAndStart": "Close task and start a new one", "export": "Export task history", "share": "Share task", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 22c255dcb0..f4d96868ba 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -4,10 +4,11 @@ "title": "Tarea", "seeMore": "Ver más", "seeLess": "Ver menos", - "tokens": "Tokens:", - "cache": "Caché:", - "apiCost": "Costo de API:", - "contextWindow": "Longitud del contexto:", + "tokens": "Tokens", + "cache": "Caché", + "apiCost": "Costo de API", + "size": "Tamaño", + "contextWindow": "Longitud del contexto", "closeAndStart": "Cerrar tarea e iniciar una nueva", "export": "Exportar historial de tareas", "delete": "Eliminar tarea (Shift + Clic para omitir confirmación)", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index e790440345..165678a3f5 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -4,10 +4,11 @@ "title": "Tâche", "seeMore": "Voir plus", "seeLess": "Voir moins", - "tokens": "Tokens :", - "cache": "Cache :", - "apiCost": "Coût API :", - "contextWindow": "Durée du contexte :", + "tokens": "Tokens", + "cache": "Cache", + "apiCost": "Coût API", + "size": "Taille", + "contextWindow": "Durée du contexte", "closeAndStart": "Fermer la tâche et en commencer une nouvelle", "export": "Exporter l'historique des tâches", "delete": "Supprimer la tâche (Shift + Clic pour ignorer la confirmation)", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index fc0785a7b8..76e5e343ef 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -4,10 +4,11 @@ "title": "कार्य", "seeMore": "अधिक देखें", "seeLess": "कम देखें", - "tokens": "Tokens:", - "cache": "कैश:", - "apiCost": "API लागत:", - "contextWindow": "संदर्भ लंबाई:", + "tokens": "Tokens", + "cache": "कैश", + "apiCost": "API लागत", + "size": "आकार", + "contextWindow": "संदर्भ लंबाई", "closeAndStart": "कार्य बंद करें और नया शुरू करें", "export": "कार्य इतिहास निर्यात करें", "delete": "कार्य हटाएं (पुष्टि को छोड़ने के लिए Shift + क्लिक)", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index ea2fcd63b5..b8e02f7fda 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -4,11 +4,12 @@ "title": "Tugas", "seeMore": "Lihat lebih banyak", "seeLess": "Lihat lebih sedikit", - "tokens": "Token:", - "cache": "Cache:", - "apiCost": "Biaya API:", + "tokens": "Token", + "cache": "Cache", + "apiCost": "Biaya API", + "size": "Ukuran", "condenseContext": "Kondensasi konteks secara cerdas", - "contextWindow": "Panjang Konteks:", + "contextWindow": "Panjang Konteks", "closeAndStart": "Tutup tugas dan mulai yang baru", "export": "Ekspor riwayat tugas", "share": "Bagikan tugas", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index e82ffee145..23e2e0c4e2 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -4,10 +4,11 @@ "title": "Attività", "seeMore": "Vedi altro", "seeLess": "Vedi meno", - "tokens": "Tokens:", - "cache": "Cache:", - "apiCost": "Costo API:", - "contextWindow": "Lunghezza del contesto:", + "tokens": "Tokens", + "cache": "Cache", + "apiCost": "Costo API", + "size": "Dimensione", + "contextWindow": "Lunghezza del contesto", "closeAndStart": "Chiudi attività e iniziane una nuova", "export": "Esporta cronologia attività", "delete": "Elimina attività (Shift + Clic per saltare la conferma)", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index bdc5df0324..98a77b0600 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -4,10 +4,11 @@ "title": "タスク", "seeMore": "もっと見る", "seeLess": "表示を減らす", - "tokens": "トークン:", - "cache": "キャッシュ:", - "apiCost": "APIコスト:", - "contextWindow": "コンテキストウィンドウ:", + "tokens": "トークン", + "cache": "キャッシュ", + "apiCost": "APIコスト", + "size": "サイズ", + "contextWindow": "コンテキストウィンドウ", "closeAndStart": "タスクを閉じて新しいタスクを開始", "export": "タスク履歴をエクスポート", "delete": "タスクを削除(Shift + クリックで確認をスキップ)", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 01fa97989a..20cd0472c3 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -4,10 +4,11 @@ "title": "작업", "seeMore": "더 보기", "seeLess": "줄여보기", - "tokens": "토큰:", - "cache": "캐시:", - "apiCost": "API 비용:", - "contextWindow": "컨텍스트 창:", + "tokens": "토큰", + "cache": "캐시", + "apiCost": "API 비용", + "size": "크기", + "contextWindow": "컨텍스트 창", "closeAndStart": "작업 닫고 새 작업 시작", "export": "작업 기록 내보내기", "delete": "작업 삭제 (Shift + 클릭으로 확인 생략)", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 70406c6e2b..519ec7bfa7 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -4,10 +4,11 @@ "title": "Taak", "seeMore": "Meer weergeven", "seeLess": "Minder weergeven", - "tokens": "Tokens:", - "cache": "Cache:", - "apiCost": "API-kosten:", - "contextWindow": "Contextlengte:", + "tokens": "Tokens", + "cache": "Cache", + "apiCost": "API-kosten", + "size": "Grootte", + "contextWindow": "Contextlengte", "closeAndStart": "Taak sluiten en een nieuwe starten", "export": "Taakgeschiedenis exporteren", "delete": "Taak verwijderen (Shift + Klik om bevestiging over te slaan)", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 6dd805b133..d02cdc9b0c 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -4,10 +4,11 @@ "title": "Zadanie", "seeMore": "Zobacz więcej", "seeLess": "Zobacz mniej", - "tokens": "Tokeny:", - "cache": "Pamięć podręczna:", - "apiCost": "Koszt API:", - "contextWindow": "Okno kontekstu:", + "tokens": "Tokeny", + "cache": "Pamięć podręczna", + "apiCost": "Koszt API", + "size": "Rozmiar", + "contextWindow": "Okno kontekstu", "closeAndStart": "Zamknij zadanie i rozpocznij nowe", "export": "Eksportuj historię zadań", "delete": "Usuń zadanie (Shift + Kliknięcie, aby pominąć potwierdzenie)", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index d8898b6e53..d441e6f7cf 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -4,10 +4,11 @@ "title": "Tarefa", "seeMore": "Ver mais", "seeLess": "Ver menos", - "tokens": "Tokens:", - "cache": "Cache:", - "apiCost": "Custo da API:", - "contextWindow": "Janela de contexto:", + "tokens": "Tokens", + "cache": "Cache", + "apiCost": "Custo da API", + "size": "Tamanho", + "contextWindow": "Janela de contexto", "closeAndStart": "Fechar tarefa e iniciar nova", "export": "Exportar histórico de tarefas", "delete": "Excluir tarefa (Shift + Clique para pular confirmação)", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 76b74b98a0..cab7366217 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -4,10 +4,11 @@ "title": "Задача", "seeMore": "Показать больше", "seeLess": "Показать меньше", - "tokens": "Токенов:", - "cache": "Кэш:", - "apiCost": "Стоимость API:", - "contextWindow": "Длина контекста:", + "tokens": "Токенов", + "cache": "Кэш", + "apiCost": "Стоимость API", + "size": "Размер", + "contextWindow": "Длина контекста", "closeAndStart": "Закрыть задачу и начать новую", "export": "Экспортировать историю задач", "delete": "Удалить задачу (Shift + клик для пропуска подтверждения)", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 272d4ddcf2..589682aa69 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -4,10 +4,11 @@ "title": "Görev", "seeMore": "Daha fazla gör", "seeLess": "Daha az gör", - "tokens": "Tokenlar:", - "cache": "Önbellek:", - "apiCost": "API Maliyeti:", - "contextWindow": "Bağlam Uzunluğu:", + "tokens": "Tokenlar", + "cache": "Önbellek", + "apiCost": "API Maliyeti", + "size": "Boyut", + "contextWindow": "Bağlam Uzunluğu", "closeAndStart": "Görevi kapat ve yeni bir görev başlat", "export": "Görev geçmişini dışa aktar", "delete": "Görevi sil (Onayı atlamak için Shift + Tıkla)", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 36045d9573..0c9f0882f4 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -4,10 +4,11 @@ "title": "Nhiệm vụ", "seeMore": "Xem thêm", "seeLess": "Thu gọn", - "tokens": "Tokens:", - "cache": "Bộ nhớ đệm:", - "apiCost": "Chi phí API:", - "contextWindow": "Chiều dài bối cảnh:", + "tokens": "Tokens", + "cache": "Bộ nhớ đệm", + "apiCost": "Chi phí API", + "size": "Kích thước", + "contextWindow": "Chiều dài bối cảnh", "closeAndStart": "Đóng nhiệm vụ và bắt đầu nhiệm vụ mới", "export": "Xuất lịch sử nhiệm vụ", "delete": "Xóa nhiệm vụ (Shift + Click để bỏ qua xác nhận)", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 85e053cf1a..2d82c7b3f7 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -4,10 +4,11 @@ "title": "任务", "seeMore": "展开", "seeLess": "收起", - "tokens": "Token 用量:", - "cache": "缓存:", - "apiCost": "API 费用:", - "contextWindow": "上下文长度:", + "tokens": "Token 用量", + "cache": "缓存", + "apiCost": "API 费用", + "size": "大小", + "contextWindow": "上下文长度", "closeAndStart": "关闭任务并开始新任务", "export": "导出任务历史", "delete": "删除任务(Shift + 点击跳过确认)", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index eaa2e74767..eb469d09b5 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -4,9 +4,10 @@ "title": "工作", "seeMore": "顯示更多", "seeLess": "顯示較少", - "tokens": "Tokens:", - "cache": "快取:", + "tokens": "Tokens", + "cache": "快取", "apiCost": "API 費用:", + "size": "大小", "contextWindow": "上下文長度:", "closeAndStart": "關閉現有工作並開始一項新的工作", "export": "匯出工作紀錄", From 31c447ad9cf8690e9154495ba18a3a83b59b4771 Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Fri, 1 Aug 2025 18:45:47 +0100 Subject: [PATCH 04/10] Fixes TaskHeader color --- webview-ui/src/components/chat/TaskHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 2ed252187f..46e093c2a2 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -76,7 +76,7 @@ const TaskHeader = ({
Date: Fri, 1 Aug 2025 18:47:34 +0100 Subject: [PATCH 05/10] Removes stray string --- webview-ui/src/i18n/locales/en/chat.json | 1 - 1 file changed, 1 deletion(-) diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 61999c4936..3b1847a10f 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -6,7 +6,6 @@ "seeLess": "See less", "tokens": "Tokens", "cache": "Cache", - "cacheSize": "Cache size", "apiCost": "API Cost", "size": "Size", "condenseContext": "Intelligently condense context", From 864890fb5da0ecf5c5990648541d2e13865952f5 Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Fri, 1 Aug 2025 20:48:45 +0100 Subject: [PATCH 06/10] Fixes tests --- .../__tests__/ContextWindowProgress.spec.tsx | 18 ++- .../chat/__tests__/TaskActions.spec.tsx | 103 +++++++----------- .../chat/__tests__/TaskHeader.spec.tsx | 18 ++- 3 files changed, 71 insertions(+), 68 deletions(-) diff --git a/webview-ui/src/__tests__/ContextWindowProgress.spec.tsx b/webview-ui/src/__tests__/ContextWindowProgress.spec.tsx index 0e5d0d6193..3c30e15bc6 100644 --- a/webview-ui/src/__tests__/ContextWindowProgress.spec.tsx +++ b/webview-ui/src/__tests__/ContextWindowProgress.spec.tsx @@ -1,6 +1,6 @@ // npm run test ContextWindowProgress.spec.tsx -import { render, screen } from "@/utils/test-utils" +import { render, screen, fireEvent } from "@/utils/test-utils" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import TaskHeader from "@src/components/chat/TaskHeader" @@ -70,6 +70,10 @@ describe("ContextWindowProgress", () => { it("renders correctly with valid inputs", () => { renderComponent({ contextTokens: 1000, contextWindow: 4000 }) + // First expand the TaskHeader to access ContextWindowProgress + const taskHeader = screen.getByText("Test task") + fireEvent.click(taskHeader) + // Check for basic elements // The context-window-label is not part of the ContextWindowProgress component // but rather part of the parent TaskHeader component in expanded state @@ -83,6 +87,10 @@ describe("ContextWindowProgress", () => { it("handles zero context window gracefully", () => { renderComponent({ contextTokens: 0, contextWindow: 0 }) + // First expand the TaskHeader to access ContextWindowProgress + const taskHeader = screen.getByText("Test task") + fireEvent.click(taskHeader) + // In the current implementation, the component is still displayed with zero values // rather than being hidden completely // The context-window-label is not part of the ContextWindowProgress component @@ -93,6 +101,10 @@ describe("ContextWindowProgress", () => { it("handles edge cases with negative values", () => { renderComponent({ contextTokens: -100, contextWindow: 4000 }) + // First expand the TaskHeader to access ContextWindowProgress + const taskHeader = screen.getByText("Test task") + fireEvent.click(taskHeader) + // Should show 0 instead of -100 expect(screen.getByTestId("context-tokens-count")).toHaveTextContent("0") // The actual context window might be different than what we pass in @@ -102,6 +114,10 @@ describe("ContextWindowProgress", () => { it("calculates percentages correctly", () => { renderComponent({ contextTokens: 1000, contextWindow: 4000 }) + // First expand the TaskHeader to access ContextWindowProgress + const taskHeader = screen.getByText("Test task") + fireEvent.click(taskHeader) + // Verify that the token count and window size are displayed correctly const tokenCount = screen.getByTestId("context-tokens-count") const windowSize = screen.getByTestId("context-window-size") diff --git a/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx b/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx index 68c564f823..31f4a9ed62 100644 --- a/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx @@ -89,19 +89,15 @@ describe("TaskActions", () => { it("renders share button when item has id", () => { render() - // Find button by its icon class - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) - expect(shareButton).toBeInTheDocument() + // ShareButton now uses lucide Share icon and shows label text + expect(screen.getByText("Share task")).toBeInTheDocument() }) it("does not render share button when item has no id", () => { render() - // Find button by its icon class - const buttons = screen.queryAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) - expect(shareButton).not.toBeDefined() + // ShareButton returns null when no item ID + expect(screen.queryByText("Share task")).not.toBeInTheDocument() }) it("renders share button even when not authenticated", () => { @@ -112,10 +108,8 @@ describe("TaskActions", () => { render() - // Find button by its icon class - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) - expect(shareButton).toBeInTheDocument() + // ShareButton should still render when not authenticated + expect(screen.getByText("Share task")).toBeInTheDocument() }) }) @@ -123,11 +117,9 @@ describe("TaskActions", () => { it("shows organization and public share options when authenticated and sharing enabled", () => { render() - // Find button by its icon class - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) - expect(shareButton).toBeDefined() - fireEvent.click(shareButton!) + // Find share button by text and click it + const shareButton = screen.getByText("Share task") + fireEvent.click(shareButton) expect(screen.getByText("Share with Organization")).toBeInTheDocument() expect(screen.getByText("Share Publicly")).toBeInTheDocument() @@ -136,11 +128,9 @@ describe("TaskActions", () => { it("sends shareCurrentTask message when organization option is selected", () => { render() - // Find button by its icon class - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) - expect(shareButton).toBeDefined() - fireEvent.click(shareButton!) + // Find share button by text and click it + const shareButton = screen.getByText("Share task") + fireEvent.click(shareButton) const orgOption = screen.getByText("Share with Organization") fireEvent.click(orgOption) @@ -154,11 +144,9 @@ describe("TaskActions", () => { it("sends shareCurrentTask message when public option is selected", () => { render() - // Find button by its icon class - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) - expect(shareButton).toBeDefined() - fireEvent.click(shareButton!) + // Find share button by text and click it + const shareButton = screen.getByText("Share task") + fireEvent.click(shareButton) const publicOption = screen.getByText("Share Publicly") fireEvent.click(publicOption) @@ -180,11 +168,9 @@ describe("TaskActions", () => { render() - // Find button by its icon class - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) - expect(shareButton).toBeDefined() - fireEvent.click(shareButton!) + // Find share button by text and click it + const shareButton = screen.getByText("Share task") + fireEvent.click(shareButton) expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument() expect(screen.getByText("Share Publicly")).toBeInTheDocument() @@ -202,11 +188,9 @@ describe("TaskActions", () => { it("shows connect to cloud option when not authenticated", () => { render() - // Find button by its icon class - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) - expect(shareButton).toBeDefined() - fireEvent.click(shareButton!) + // Find share button by text and click it + const shareButton = screen.getByText("Share task") + fireEvent.click(shareButton) expect(screen.getByText("Connect to Roo Code Cloud")).toBeInTheDocument() expect(screen.getByText("Sign in to Roo Code Cloud to share tasks")).toBeInTheDocument() @@ -216,11 +200,9 @@ describe("TaskActions", () => { it("does not show organization and public options when not authenticated", () => { render() - // Find button by its icon class - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) - expect(shareButton).toBeDefined() - fireEvent.click(shareButton!) + // Find share button by text and click it + const shareButton = screen.getByText("Share task") + fireEvent.click(shareButton) expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument() expect(screen.queryByText("Share Publicly")).not.toBeInTheDocument() @@ -229,11 +211,9 @@ describe("TaskActions", () => { it("sends rooCloudSignIn message when connect to cloud is selected", () => { render() - // Find button by its icon class - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) - expect(shareButton).toBeDefined() - fireEvent.click(shareButton!) + // Find share button by text and click it + const shareButton = screen.getByText("Share task") + fireEvent.click(shareButton) const connectOption = screen.getByText("Connect") fireEvent.click(connectOption) @@ -253,9 +233,8 @@ describe("TaskActions", () => { render() - // Find button by its icon class - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) + // Find share button by text + const shareButton = screen.getByText("Share task").closest("button") expect(shareButton).toBeInTheDocument() expect(shareButton).toBeDisabled() @@ -303,10 +282,8 @@ describe("TaskActions", () => { const { rerender } = render() // Click share button to open connect modal - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) - expect(shareButton).toBeDefined() - fireEvent.click(shareButton!) + const shareButton = screen.getByText("Share task") + fireEvent.click(shareButton) // Click connect button to initiate authentication const connectButton = screen.getByText("Connect") @@ -353,12 +330,11 @@ describe("TaskActions", () => { }) }) - it("renders delete button and file size when item has size", () => { + it("renders delete button when item has size", () => { render() const deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)") expect(deleteButton).toBeInTheDocument() - expect(screen.getByText("1024 B")).toBeInTheDocument() }) it("does not render delete button when item has no size", () => { @@ -375,10 +351,9 @@ describe("TaskActions", () => { render() // Find buttons by their labels/icons - const buttons = screen.getAllByRole("button") - const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) + const shareButton = screen.getByText("Share task").closest("button") const exportButton = screen.getByLabelText("Export task history") - const copyButton = buttons.find((btn) => btn.querySelector(".codicon-copy")) + const copyButton = screen.getByLabelText("history:copyPrompt") const deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)") // Share, export, and copy buttons should be enabled regardless of buttonsDisabled @@ -393,10 +368,9 @@ describe("TaskActions", () => { // Test with buttonsDisabled = false const { rerender } = render() - let buttons = screen.getAllByRole("button") - let shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) + let shareButton = screen.getByText("Share task").closest("button") let exportButton = screen.getByLabelText("Export task history") - let copyButton = buttons.find((btn) => btn.querySelector(".codicon-copy")) + let copyButton = screen.getByLabelText("history:copyPrompt") let deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)") expect(shareButton).not.toBeDisabled() @@ -407,10 +381,9 @@ describe("TaskActions", () => { // Test with buttonsDisabled = true rerender() - buttons = screen.getAllByRole("button") - shareButton = buttons.find((btn) => btn.querySelector(".codicon-link")) + shareButton = screen.getByText("Share task").closest("button") exportButton = screen.getByLabelText("Export task history") - copyButton = buttons.find((btn) => btn.querySelector(".codicon-copy")) + copyButton = screen.getByLabelText("history:copyPrompt") deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)") // Share, export, and copy remain enabled diff --git a/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx b/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx index d0bdb6f2de..d89305348e 100644 --- a/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx @@ -90,9 +90,13 @@ describe("TaskHeader", () => { expect(screen.queryByText(/\$/)).not.toBeInTheDocument() }) - it("should render the condense context button", () => { + it("should render the condense context button when expanded", () => { renderTaskHeader() - // Find the button that contains the FoldVertical icon + // First click to expand the task header + const taskHeader = screen.getByText("Test task") + fireEvent.click(taskHeader) + + // Now find the condense button in the expanded state const buttons = screen.getAllByRole("button") const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical")) expect(condenseButton).toBeDefined() @@ -102,6 +106,11 @@ describe("TaskHeader", () => { it("should call handleCondenseContext when condense context button is clicked", () => { const handleCondenseContext = vi.fn() renderTaskHeader({ handleCondenseContext }) + + // First click to expand the task header + const taskHeader = screen.getByText("Test task") + fireEvent.click(taskHeader) + // Find the button that contains the FoldVertical icon const buttons = screen.getAllByRole("button") const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical")) @@ -113,6 +122,11 @@ describe("TaskHeader", () => { it("should disable the condense context button when buttonsDisabled is true", () => { const handleCondenseContext = vi.fn() renderTaskHeader({ buttonsDisabled: true, handleCondenseContext }) + + // First click to expand the task header + const taskHeader = screen.getByText("Test task") + fireEvent.click(taskHeader) + // Find the button that contains the FoldVertical icon const buttons = screen.getAllByRole("button") const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical")) From a2eb99cead8f64f9eebb6b49a1e3cfa5d093401c Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Mon, 4 Aug 2025 14:32:16 +0100 Subject: [PATCH 07/10] Iterates on visual details --- .../src/components/chat/ShareButton.tsx | 8 +- webview-ui/src/components/chat/TaskHeader.tsx | 103 +++++++++++------- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/webview-ui/src/components/chat/ShareButton.tsx b/webview-ui/src/components/chat/ShareButton.tsx index e77e08808e..cec0be6474 100644 --- a/webview-ui/src/components/chat/ShareButton.tsx +++ b/webview-ui/src/components/chat/ShareButton.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useRef } from "react" import { useTranslation } from "react-i18next" -import { Share } from "lucide-react" +import { SquareArrowOutUpRightIcon } from "lucide-react" import type { HistoryItem, ShareVisibility } from "@roo-code/types" import { TelemetryEventName } from "@roo-code/types" @@ -165,7 +165,7 @@ export const ShareButton = ({ item, disabled = false, showLabel = false }: Share : "h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground" } onClick={handleShareButtonClick}> - + {showLabel && {t("chat:task.share")}} @@ -233,8 +233,8 @@ export const ShareButton = ({ item, disabled = false, showLabel = false }: Share : "h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground" } onClick={handleShareButtonClick}> - - {showLabel && {t("chat:task.share")}} + + {showLabel && {t("chat:task.share")}} )} diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 46e093c2a2..82c224abe2 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -1,7 +1,7 @@ import { memo, useRef, useState } from "react" import { useWindowSize } from "react-use" import { useTranslation } from "react-i18next" -import { CloudUpload, CloudDownload, FoldVertical, ChevronUp } from "lucide-react" +import { FoldVertical, ChevronUp, ChevronDown } from "lucide-react" import prettyBytes from "pretty-bytes" import type { ClineMessage } from "@roo-code/types" @@ -75,10 +75,9 @@ const TaskHeader = ({
{ @@ -109,29 +108,65 @@ const TaskHeader = ({ {isTaskExpanded && Task Details} {!isTaskExpanded && }
- {isTaskExpanded && ( -
e.stopPropagation()}> - - - -
- )} -
- {!isTaskExpanded && ( -
e.stopPropagation()}> - +
e.stopPropagation()}> + + +
- )} +
{!isTaskExpanded && contextWindow > 0 && (
e.stopPropagation()}> - - {formatLargeNumber(contextTokens || 0)} / {formatLargeNumber(contextWindow)} - +
e.stopPropagation()}> + +
+ +
+ {t("chat:tokenProgress.tokensUsed", { + used: formatLargeNumber(contextTokens || 0), + total: formatLargeNumber(contextWindow), + })} +
+ {(() => { + const maxTokens = model + ? getModelMaxOutputTokens({ modelId, model, settings: apiConfiguration }) + : 0 + const reservedForOutput = maxTokens || 0 + const availableSpace = contextWindow - (contextTokens || 0) - reservedForOutput + + return ( + <> + {reservedForOutput > 0 && ( +
+ {t("chat:tokenProgress.reservedForResponse", { + amount: formatLargeNumber(reservedForOutput), + })} +
+ )} + {availableSpace > 0 && ( +
+ {t("chat:tokenProgress.availableSpace", { + amount: formatLargeNumber(availableSpace), + })} +
+ )} + + ) + })()} +
+ } + side="top" + sideOffset={8}> + + {formatLargeNumber(contextTokens || 0)} / {formatLargeNumber(contextWindow)} + + {!!totalCost && ${totalCost.toFixed(2)}}
)} @@ -143,7 +178,7 @@ const TaskHeader = ({ className="-mt-0.5 text-vscode-font-size overflow-y-auto break-words break-anywhere relative">
0 && ( {t("chat:task.contextWindow")}
+ className={`max-w-64 -mt-0.5 flex ${windowWidth < 400 ? "flex-col" : "flex-row"} gap-1`}> - + {t("chat:task.tokens")} @@ -205,22 +240,16 @@ const TaskHeader = ({ {((typeof cacheReads === "number" && cacheReads > 0) || (typeof cacheWrites === "number" && cacheWrites > 0)) && ( - + {t("chat:task.cache")}
{typeof cacheWrites === "number" && cacheWrites > 0 && ( - - - {formatLargeNumber(cacheWrites)} - + ↑ {formatLargeNumber(cacheWrites)} )} {typeof cacheReads === "number" && cacheReads > 0 && ( - - - {formatLargeNumber(cacheReads)} - + ↓ {formatLargeNumber(cacheReads)} )}
@@ -229,7 +258,7 @@ const TaskHeader = ({ {!!totalCost && ( - + {t("chat:task.apiCost")} @@ -242,7 +271,7 @@ const TaskHeader = ({ {((typeof cacheReads === "number" && cacheReads > 0) || (typeof cacheWrites === "number" && cacheWrites > 0)) && ( - + {t("chat:task.cache")} From 7a77b9158563c6a8f46abc2cf671fa07a91e3ff9 Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Mon, 4 Aug 2025 15:44:04 +0100 Subject: [PATCH 08/10] More visual tweaks --- .../src/components/chat/TaskActions.tsx | 83 +++++++++---------- webview-ui/src/components/chat/TaskHeader.tsx | 21 ++--- 2 files changed, 47 insertions(+), 57 deletions(-) diff --git a/webview-ui/src/components/chat/TaskActions.tsx b/webview-ui/src/components/chat/TaskActions.tsx index 19014b0363..1b192219ad 100644 --- a/webview-ui/src/components/chat/TaskActions.tsx +++ b/webview-ui/src/components/chat/TaskActions.tsx @@ -21,54 +21,47 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => { const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard() return ( -
- {/* Share button with label on the left */} -
- -
- - {/* Other action buttons on the right */} -
+
+ vscode.postMessage({ type: "exportCurrentTask" })} + /> + {item?.task && ( vscode.postMessage({ type: "exportCurrentTask" })} + iconClass={showCopyFeedback ? "codicon-check" : "codicon-copy"} + title={t("history:copyPrompt")} + onClick={(e) => copyWithFeedback(item.task, e)} /> - {item?.task && ( - copyWithFeedback(item.task, e)} - /> - )} - {!!item?.size && item.size > 0 && ( - <> -
- { - e.stopPropagation() + )} + {!!item?.size && item.size > 0 && ( + <> +
+ { + e.stopPropagation() - if (e.shiftKey) { - vscode.postMessage({ type: "deleteTaskWithId", text: item.id }) - } else { - setDeleteTaskId(item.id) - } - }} - /> -
- {deleteTaskId && ( - !open && setDeleteTaskId(null)} - open - /> - )} - - )} -
+ if (e.shiftKey) { + vscode.postMessage({ type: "deleteTaskWithId", text: item.id }) + } else { + setDeleteTaskId(item.id) + } + }} + /> +
+ {deleteTaskId && ( + !open && setDeleteTaskId(null)} + open + /> + )} + + )} +
) } diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 82c224abe2..9636db8eba 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -1,5 +1,4 @@ import { memo, useRef, useState } from "react" -import { useWindowSize } from "react-use" import { useTranslation } from "react-i18next" import { FoldVertical, ChevronUp, ChevronDown } from "lucide-react" import prettyBytes from "pretty-bytes" @@ -17,7 +16,6 @@ import { useSelectedModel } from "@/components/ui/hooks/useSelectedModel" import Thumbnails from "../common/Thumbnails" import { TaskActions } from "./TaskActions" -import { ShareButton } from "./ShareButton" import { ContextWindowProgress } from "./ContextWindowProgress" import { Mention } from "./Mention" import { TodoListDisplay } from "./TodoListDisplay" @@ -56,8 +54,6 @@ const TaskHeader = ({ const textRef = useRef(null) const contextWindow = model?.contextWindow || 1 - const { width: windowWidth } = useWindowSize() - const condenseButton = ( @@ -232,7 +233,8 @@ export const ShareButton = ({ item, disabled = false, showLabel = false }: Share ? "h-7 px-2 hover:bg-vscode-toolbar-hoverBackground" : "h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground" } - onClick={handleShareButtonClick}> + onClick={handleShareButtonClick} + data-testid="share-button"> {showLabel && {t("chat:task.share")}} diff --git a/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx b/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx index 31f4a9ed62..4c1138944d 100644 --- a/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx @@ -89,15 +89,17 @@ describe("TaskActions", () => { it("renders share button when item has id", () => { render() - // ShareButton now uses lucide Share icon and shows label text - expect(screen.getByText("Share task")).toBeInTheDocument() + // ShareButton now uses data-testid for reliable testing + const shareButton = screen.getByTestId("share-button") + expect(shareButton).toBeInTheDocument() }) it("does not render share button when item has no id", () => { render() // ShareButton returns null when no item ID - expect(screen.queryByText("Share task")).not.toBeInTheDocument() + const shareButton = screen.queryByTestId("share-button") + expect(shareButton).toBeNull() }) it("renders share button even when not authenticated", () => { @@ -109,7 +111,8 @@ describe("TaskActions", () => { render() // ShareButton should still render when not authenticated - expect(screen.getByText("Share task")).toBeInTheDocument() + const shareButton = screen.getByTestId("share-button") + expect(shareButton).toBeInTheDocument() }) }) @@ -117,8 +120,8 @@ describe("TaskActions", () => { it("shows organization and public share options when authenticated and sharing enabled", () => { render() - // Find share button by text and click it - const shareButton = screen.getByText("Share task") + // Find share button by its test ID and click it + const shareButton = screen.getByTestId("share-button") fireEvent.click(shareButton) expect(screen.getByText("Share with Organization")).toBeInTheDocument() @@ -128,8 +131,8 @@ describe("TaskActions", () => { it("sends shareCurrentTask message when organization option is selected", () => { render() - // Find share button by text and click it - const shareButton = screen.getByText("Share task") + // Find share button by its test ID and click it + const shareButton = screen.getByTestId("share-button") fireEvent.click(shareButton) const orgOption = screen.getByText("Share with Organization") @@ -144,8 +147,8 @@ describe("TaskActions", () => { it("sends shareCurrentTask message when public option is selected", () => { render() - // Find share button by text and click it - const shareButton = screen.getByText("Share task") + // Find share button by its test ID and click it + const shareButton = screen.getByTestId("share-button") fireEvent.click(shareButton) const publicOption = screen.getByText("Share Publicly") @@ -168,8 +171,8 @@ describe("TaskActions", () => { render() - // Find share button by text and click it - const shareButton = screen.getByText("Share task") + // Find share button by its test ID and click it + const shareButton = screen.getByTestId("share-button") fireEvent.click(shareButton) expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument() @@ -188,8 +191,8 @@ describe("TaskActions", () => { it("shows connect to cloud option when not authenticated", () => { render() - // Find share button by text and click it - const shareButton = screen.getByText("Share task") + // Find share button by its test ID and click it + const shareButton = screen.getByTestId("share-button") fireEvent.click(shareButton) expect(screen.getByText("Connect to Roo Code Cloud")).toBeInTheDocument() @@ -200,8 +203,8 @@ describe("TaskActions", () => { it("does not show organization and public options when not authenticated", () => { render() - // Find share button by text and click it - const shareButton = screen.getByText("Share task") + // Find share button by its test ID and click it + const shareButton = screen.getByTestId("share-button") fireEvent.click(shareButton) expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument() @@ -211,8 +214,8 @@ describe("TaskActions", () => { it("sends rooCloudSignIn message when connect to cloud is selected", () => { render() - // Find share button by text and click it - const shareButton = screen.getByText("Share task") + // Find share button by its test ID and click it + const shareButton = screen.getByTestId("share-button") fireEvent.click(shareButton) const connectOption = screen.getByText("Connect") @@ -233,8 +236,8 @@ describe("TaskActions", () => { render() - // Find share button by text - const shareButton = screen.getByText("Share task").closest("button") + // Find share button by its test ID + const shareButton = screen.getByTestId("share-button") expect(shareButton).toBeInTheDocument() expect(shareButton).toBeDisabled() @@ -282,7 +285,7 @@ describe("TaskActions", () => { const { rerender } = render() // Click share button to open connect modal - const shareButton = screen.getByText("Share task") + const shareButton = screen.getByTestId("share-button") fireEvent.click(shareButton) // Click connect button to initiate authentication @@ -350,8 +353,8 @@ describe("TaskActions", () => { it("keeps share, export, and copy buttons enabled but disables delete button when buttonsDisabled is true", () => { render() - // Find buttons by their labels/icons - const shareButton = screen.getByText("Share task").closest("button") + // Find buttons by their labels/test IDs + const shareButton = screen.getByTestId("share-button") const exportButton = screen.getByLabelText("Export task history") const copyButton = screen.getByLabelText("history:copyPrompt") const deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)") @@ -368,7 +371,7 @@ describe("TaskActions", () => { // Test with buttonsDisabled = false const { rerender } = render() - let shareButton = screen.getByText("Share task").closest("button") + let shareButton = screen.getByTestId("share-button") let exportButton = screen.getByLabelText("Export task history") let copyButton = screen.getByLabelText("history:copyPrompt") let deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)") @@ -381,7 +384,7 @@ describe("TaskActions", () => { // Test with buttonsDisabled = true rerender() - shareButton = screen.getByText("Share task").closest("button") + shareButton = screen.getByTestId("share-button") exportButton = screen.getByLabelText("Export task history") copyButton = screen.getByLabelText("history:copyPrompt") deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)")