mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
refactor: extract setTaskTitle handler to separate module
This commit is contained in:
parent
9ef7b72d70
commit
1440281ee4
3 changed files with 108 additions and 55 deletions
102
src/core/webview/taskTitleHandler.ts
Normal file
102
src/core/webview/taskTitleHandler.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import type { HistoryItem } from "@roo-code/types"
|
||||
|
||||
import type { ClineProvider } from "./ClineProvider"
|
||||
import type { WebviewMessage } from "../../shared/WebviewMessage"
|
||||
|
||||
const MAX_TITLE_LENGTH = 255
|
||||
|
||||
/**
|
||||
* Sanitizes and normalizes a title string:
|
||||
* - Removes control characters
|
||||
* - Normalizes whitespace
|
||||
* - Trims leading/trailing whitespace
|
||||
* - Truncates if too long
|
||||
* - Returns undefined for empty strings
|
||||
*/
|
||||
function normalizeTitle(title: string | undefined | null, ids?: string[]): string | undefined {
|
||||
const rawTitle = title ?? ""
|
||||
|
||||
// Sanitize: remove control characters and normalize whitespace
|
||||
const sanitized = rawTitle
|
||||
// eslint-disable-next-line no-control-regex
|
||||
.replace(/[\x00-\x1F\x7F-\x9F]/g, "") // Remove control characters
|
||||
.replace(/\s+/g, " ") // Normalize whitespace
|
||||
.trim()
|
||||
|
||||
// Clear empty titles
|
||||
if (sanitized.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Truncate if too long
|
||||
if (sanitized.length > MAX_TITLE_LENGTH) {
|
||||
const truncated = sanitized.slice(0, MAX_TITLE_LENGTH).trim()
|
||||
console.warn(
|
||||
`[setTaskTitle] Title truncated from ${sanitized.length} to ${MAX_TITLE_LENGTH} chars for task(s): ${ids?.join(", ") ?? "unknown"}`,
|
||||
)
|
||||
return truncated
|
||||
}
|
||||
|
||||
return sanitized
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the setTaskTitle webview message.
|
||||
* Updates task titles for one or more history items, with deduplication and no-op detection.
|
||||
*/
|
||||
export async function handleSetTaskTitle(provider: ClineProvider, message: WebviewMessage): Promise<void> {
|
||||
// 1. Validate and deduplicate incoming task IDs
|
||||
const ids = Array.isArray(message.ids)
|
||||
? Array.from(new Set(message.ids.filter((id): id is string => typeof id === "string" && id.trim().length > 0)))
|
||||
: []
|
||||
|
||||
if (ids.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Normalize the incoming title (with sanitization and truncation)
|
||||
const normalizedTitle = normalizeTitle(message.text, ids)
|
||||
|
||||
// 3. Get task history from state
|
||||
const { taskHistory } = await provider.getState()
|
||||
if (!Array.isArray(taskHistory) || taskHistory.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// 4. Create a map for O(1) lookups
|
||||
const historyById = new Map(taskHistory.map((item) => [item.id, item] as const))
|
||||
|
||||
// 5. Process each ID, skipping no-ops
|
||||
let hasUpdates = false
|
||||
|
||||
for (const id of ids) {
|
||||
const existingItem = historyById.get(id)
|
||||
if (!existingItem) {
|
||||
console.warn(`[setTaskTitle] Unable to locate task history item with id ${id}`)
|
||||
continue
|
||||
}
|
||||
|
||||
// Normalize existing title for comparison
|
||||
const normalizedExistingTitle =
|
||||
existingItem.title && existingItem.title.trim().length > 0 ? existingItem.title.trim() : undefined
|
||||
|
||||
// Skip if title is unchanged
|
||||
if (normalizedExistingTitle === normalizedTitle) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update the history item
|
||||
const updatedItem: HistoryItem = {
|
||||
...existingItem,
|
||||
title: normalizedTitle,
|
||||
}
|
||||
|
||||
await provider.updateTaskHistory(updatedItem)
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
// 6. Sync webview state if there were changes
|
||||
if (hasUpdates) {
|
||||
await provider.postStateToWebview()
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ import {
|
|||
type Language,
|
||||
type GlobalState,
|
||||
type ClineMessage,
|
||||
type HistoryItem,
|
||||
type TelemetrySetting,
|
||||
type UserSettingsConfig,
|
||||
type ModelRecord,
|
||||
|
|
@ -33,6 +32,7 @@ import { ClineProvider } from "./ClineProvider"
|
|||
import { BrowserSessionPanelManager } from "./BrowserSessionPanelManager"
|
||||
import { handleCheckpointRestoreOperation } from "./checkpointRestoreHandler"
|
||||
import { generateErrorDiagnostics } from "./diagnosticsHandler"
|
||||
import { handleSetTaskTitle } from "./taskTitleHandler"
|
||||
import { changeLanguage, t } from "../../i18n"
|
||||
import { Package } from "../../shared/package"
|
||||
import { type RouterName, toRouterName } from "../../shared/api"
|
||||
|
|
@ -729,57 +729,9 @@ export const webviewMessageHandler = async (
|
|||
vscode.window.showErrorMessage(t("common:errors.share_task_failed"))
|
||||
}
|
||||
break
|
||||
case "setTaskTitle": {
|
||||
const ids = Array.isArray(message.ids)
|
||||
? Array.from(
|
||||
new Set(
|
||||
message.ids.filter((id): id is string => typeof id === "string" && id.trim().length > 0),
|
||||
),
|
||||
)
|
||||
: []
|
||||
if (ids.length === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
const rawTitle = message.text ?? ""
|
||||
const trimmedTitle = rawTitle.trim()
|
||||
const normalizedTitle = trimmedTitle.length > 0 ? trimmedTitle : undefined
|
||||
const { taskHistory } = await provider.getState()
|
||||
if (!Array.isArray(taskHistory) || taskHistory.length === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
let hasUpdates = false
|
||||
const historyById = new Map(taskHistory.map((item) => [item.id, item] as const))
|
||||
|
||||
for (const id of ids) {
|
||||
const existingItem = historyById.get(id)
|
||||
if (!existingItem) {
|
||||
console.warn(`[setTaskTitle] Unable to locate task history item with id ${id}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const normalizedExistingTitle =
|
||||
existingItem.title && existingItem.title.trim().length > 0 ? existingItem.title.trim() : undefined
|
||||
if (normalizedExistingTitle === normalizedTitle) {
|
||||
continue
|
||||
}
|
||||
|
||||
const updatedItem: HistoryItem = {
|
||||
...existingItem,
|
||||
title: normalizedTitle,
|
||||
}
|
||||
|
||||
await provider.updateTaskHistory(updatedItem)
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
if (hasUpdates) {
|
||||
await provider.postStateToWebview()
|
||||
}
|
||||
|
||||
case "setTaskTitle":
|
||||
await handleSetTaskTitle(provider, message)
|
||||
break
|
||||
}
|
||||
case "showTaskWithId":
|
||||
provider.showTaskWithId(message.text!)
|
||||
break
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ const TaskHeader = ({
|
|||
}}
|
||||
aria-label={t(tooltipKey)}
|
||||
data-testid="task-title-edit-button">
|
||||
<Pencil size={16} />
|
||||
<Pencil size={16} className="opacity-0 group-hover:opacity-100" />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
)
|
||||
|
|
@ -285,7 +285,7 @@ const TaskHeader = ({
|
|||
|
||||
if (currentTitle.length > 0) {
|
||||
return (
|
||||
<span className="truncate text-base" data-testid="task-title-text">
|
||||
<span className="block truncate text-base" data-testid="task-title-text">
|
||||
{currentTitle}
|
||||
</span>
|
||||
)
|
||||
|
|
@ -389,8 +389,7 @@ const TaskHeader = ({
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center shrink-0 ml-2" onClick={(e) => e.stopPropagation()}>
|
||||
<StandardTooltip
|
||||
content={isTaskExpanded ? t("chat:task.collapse") : t("chat:task.expand")}>
|
||||
<StandardTooltip content={isTaskExpanded ? t("chat:task.collapse") : t("chat:task.expand")}>
|
||||
<button
|
||||
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">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue