feat: implement enhanced chat history management with favorites and custom naming

- Add isFavorite and customName fields to HistoryItem type with backward compatibility
- Create FavoriteButton component with star icon and toggle functionality
- Create RenameButton component with inline editing capability
- Update TaskItemHeader to include favorite and rename buttons
- Add favorites filtering toggle to HistoryView
- Enhance useTaskSearch hook with favorites filtering and custom name search
- Update webview message handlers for toggleTaskFavorite and renameTask operations
- Add comprehensive test coverage for new components and functionality
- Maintain backward compatibility with existing history data

Resolves #6410
This commit is contained in:
Roo Code 2025-07-30 03:20:12 +00:00
parent 8f7ac57e47
commit f677db3501
14 changed files with 572 additions and 7 deletions

View file

@ -17,6 +17,8 @@ export const historyItemSchema = z.object({
size: z.number().optional(),
workspace: z.string().optional(),
mode: z.string().optional(),
isFavorite: z.boolean().optional(),
customName: z.string().optional(),
})
export type HistoryItem = z.infer<typeof historyItemSchema>

View file

@ -95,6 +95,8 @@ export async function taskMetadata({
size: taskDirSize,
workspace,
mode,
isFavorite: false, // Initialize as not favorited
customName: undefined, // Initialize with no custom name
}
return { historyItem, tokenUsage }

View file

@ -2558,5 +2558,57 @@ export const webviewMessageHandler = async (
}
break
}
case "toggleTaskFavorite": {
if (message.taskId) {
try {
// Get the task and update its favorite status
const { historyItem } = await provider.getTaskWithId(message.taskId)
if (historyItem) {
// Toggle the favorite status
const updatedHistoryItem = {
...historyItem,
isFavorite: !historyItem.isFavorite,
}
// Update the task metadata
await provider.updateTaskHistory(updatedHistoryItem)
// Refresh the webview state to reflect the change
await provider.postStateToWebview()
}
} catch (error) {
provider.log(
`Error toggling task favorite: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.toggle_favorite_failed"))
}
}
break
}
case "renameTask": {
if (message.taskId && message.newName !== undefined) {
try {
// Get the task and update its custom name
const { historyItem } = await provider.getTaskWithId(message.taskId)
if (historyItem) {
// Update the custom name (empty string means remove custom name)
const updatedHistoryItem = {
...historyItem,
customName: message.newName.trim() || undefined,
}
// Update the task metadata
await provider.updateTaskHistory(updatedHistoryItem)
// Refresh the webview state to reflect the change
await provider.postStateToWebview()
}
} catch (error) {
provider.log(`Error renaming task: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`)
vscode.window.showErrorMessage(t("common:errors.rename_task_failed"))
}
}
break
}
}
}

View file

@ -78,6 +78,8 @@
"command_already_exists": "Command \"{{commandName}}\" already exists",
"create_command_failed": "Failed to create command",
"command_template_content": "---\ndescription: \"Brief description of what this command does\"\n---\n\nThis is a new slash command. Edit this file to customize the command behavior.",
"toggle_favorite_failed": "Failed to toggle task favorite status",
"rename_task_failed": "Failed to rename task",
"claudeCode": {
"processExited": "Claude Code process exited with code {{exitCode}}.",
"errorOutput": "Error output: {{output}}",

View file

@ -208,6 +208,8 @@ export interface WebviewMessage {
| "deleteCommand"
| "createCommand"
| "insertTextIntoTextarea"
| "toggleTaskFavorite"
| "renameTask"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
@ -270,6 +272,8 @@ export interface WebviewMessage {
codebaseIndexGeminiApiKey?: string
codebaseIndexMistralApiKey?: string
}
taskId?: string
newName?: string
}
export const checkoutDiffPayloadSchema = z.object({

View file

@ -0,0 +1,28 @@
import React from "react"
import { StandardTooltip } from "@/components/ui"
interface FavoriteButtonProps {
isFavorite: boolean
onToggleFavorite: () => void
className?: string
}
export const FavoriteButton: React.FC<FavoriteButtonProps> = ({ isFavorite, onToggleFavorite, className = "" }) => {
return (
<StandardTooltip content={isFavorite ? "Remove from favorites" : "Add to favorites"}>
<button
onClick={(e) => {
e.stopPropagation()
onToggleFavorite()
}}
className={`p-1 rounded hover:bg-vscode-toolbar-hoverBackground transition-colors ${className}`}
data-testid="favorite-button">
<span
className={`codicon ${
isFavorite ? "codicon-star-full" : "codicon-star-empty"
} text-sm ${isFavorite ? "text-yellow-400" : "text-vscode-descriptionForeground"}`}
/>
</button>
</StandardTooltip>
)
}

View file

@ -37,6 +37,10 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
setLastNonRelevantSort,
showAllWorkspaces,
setShowAllWorkspaces,
showFavoritesOnly,
setShowFavoritesOnly,
handleToggleFavorite,
handleRename,
} = useTaskSearch()
const { t } = useAppTranslation()
@ -152,6 +156,27 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
</SelectItem>
</SelectContent>
</Select>
<Select
value={showFavoritesOnly ? "favorites" : "all"}
onValueChange={(value) => setShowFavoritesOnly(value === "favorites")}>
<SelectTrigger className="flex-1">
<SelectValue>{showFavoritesOnly ? "Favorites Only" : "All Tasks"}</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="all">
<div className="flex items-center gap-2">
<span className="codicon codicon-list-unordered" />
All Tasks
</div>
</SelectItem>
<SelectItem value="favorites">
<div className="flex items-center gap-2">
<span className="codicon codicon-star-full text-yellow-400" />
Favorites Only
</div>
</SelectItem>
</SelectContent>
</Select>
<Select value={sortOption} onValueChange={(value) => setSortOption(value as SortOption)}>
<SelectTrigger className="flex-1">
<SelectValue>
@ -243,6 +268,8 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
isSelected={selectedTaskIds.includes(item.id)}
onToggleSelection={toggleTaskSelection}
onDelete={setDeleteTaskId}
onToggleFavorite={handleToggleFavorite}
onRename={handleRename}
className="m-2 mr-0"
/>
)}

View file

@ -0,0 +1,80 @@
import React, { useState } from "react"
import { StandardTooltip } from "@/components/ui"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
interface RenameButtonProps {
currentName: string
onRename: (newName: string) => void
className?: string
}
export const RenameButton: React.FC<RenameButtonProps> = ({ currentName, onRename, className = "" }) => {
const [isEditing, setIsEditing] = useState(false)
const [editValue, setEditValue] = useState(currentName)
const handleStartEdit = (e: React.MouseEvent) => {
e.stopPropagation()
setEditValue(currentName)
setIsEditing(true)
}
const handleSave = () => {
const trimmedValue = editValue.trim()
if (trimmedValue !== currentName) {
onRename(trimmedValue)
}
setIsEditing(false)
}
const handleCancel = () => {
setEditValue(currentName)
setIsEditing(false)
}
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
handleSave()
} else if (e.key === "Escape") {
handleCancel()
}
}
if (isEditing) {
return (
<div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
<VSCodeTextField
value={editValue}
onInput={(e) => setEditValue((e.target as HTMLInputElement).value)}
onKeyDown={handleKeyDown}
className="text-xs"
style={{ minWidth: "120px" }}
autoFocus
data-testid="rename-input"
/>
<button
onClick={handleSave}
className="p-1 rounded hover:bg-vscode-toolbar-hoverBackground transition-colors"
data-testid="rename-save">
<span className="codicon codicon-check text-xs text-green-400" />
</button>
<button
onClick={handleCancel}
className="p-1 rounded hover:bg-vscode-toolbar-hoverBackground transition-colors"
data-testid="rename-cancel">
<span className="codicon codicon-close text-xs text-red-400" />
</button>
</div>
)
}
return (
<StandardTooltip content="Rename task">
<button
onClick={handleStartEdit}
className={`p-1 rounded hover:bg-vscode-toolbar-hoverBackground transition-colors ${className}`}
data-testid="rename-button">
<span className="codicon codicon-edit text-sm text-vscode-descriptionForeground" />
</button>
</StandardTooltip>
)
}

View file

@ -20,6 +20,8 @@ interface TaskItemProps {
isSelected?: boolean
onToggleSelection?: (taskId: string, isSelected: boolean) => void
onDelete?: (taskId: string) => void
onToggleFavorite?: (taskId: string) => void
onRename?: (taskId: string, newName: string) => void
className?: string
}
@ -31,6 +33,8 @@ const TaskItem = ({
isSelected = false,
onToggleSelection,
onDelete,
onToggleFavorite,
onRename,
className,
}: TaskItemProps) => {
const handleClick = () => {
@ -70,7 +74,13 @@ const TaskItem = ({
<div className="flex-1 min-w-0">
{/* Header with metadata */}
<TaskItemHeader item={item} isSelectionMode={isSelectionMode} onDelete={onDelete} />
<TaskItemHeader
item={item}
isSelectionMode={isSelectionMode}
onDelete={onDelete}
onToggleFavorite={onToggleFavorite}
onRename={onRename}
/>
{/* Task content */}
<div
@ -80,7 +90,7 @@ const TaskItem = ({
})}
data-testid="task-content"
{...(item.highlight ? { dangerouslySetInnerHTML: { __html: item.highlight } } : {})}>
{item.highlight ? undefined : item.task}
{item.highlight ? undefined : item.customName || item.task}
</div>
{/* Task Item Footer */}

View file

@ -2,15 +2,27 @@ import React from "react"
import type { HistoryItem } from "@roo-code/types"
import { formatDate } from "@/utils/format"
import { DeleteButton } from "./DeleteButton"
import { FavoriteButton } from "./FavoriteButton"
import { RenameButton } from "./RenameButton"
import { cn } from "@/lib/utils"
export interface TaskItemHeaderProps {
item: HistoryItem
isSelectionMode: boolean
onDelete?: (taskId: string) => void
onToggleFavorite?: (taskId: string) => void
onRename?: (taskId: string, newName: string) => void
}
const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({ item, isSelectionMode, onDelete }) => {
const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({
item,
isSelectionMode,
onDelete,
onToggleFavorite,
onRename,
}) => {
const displayName = item.customName || item.task
return (
<div
className={cn("flex justify-between items-center", {
@ -22,11 +34,23 @@ const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({ item, isSelectionMode,
<span className="text-vscode-descriptionForeground font-medium text-sm uppercase">
{formatDate(item.ts)}
</span>
{item.isFavorite && (
<span className="codicon codicon-star-full text-yellow-400 text-xs" title="Favorited" />
)}
</div>
{/* Action Buttons */}
{!isSelectionMode && (
<div className="flex flex-row gap-0 items-center opacity-20 group-hover:opacity-50 hover:opacity-100">
{onToggleFavorite && (
<FavoriteButton
isFavorite={item.isFavorite || false}
onToggleFavorite={() => onToggleFavorite(item.id)}
/>
)}
{onRename && (
<RenameButton currentName={displayName} onRename={(newName) => onRename(item.id, newName)} />
)}
{onDelete && <DeleteButton itemId={item.id} onDelete={onDelete} />}
</div>
)}

View file

@ -0,0 +1,81 @@
import { render, screen, fireEvent } from "@testing-library/react"
import { describe, it, expect, vi } from "vitest"
import { FavoriteButton } from "../FavoriteButton"
import { TooltipProvider } from "@/components/ui/tooltip"
const renderWithTooltipProvider = (component: React.ReactElement) => {
return render(<TooltipProvider>{component}</TooltipProvider>)
}
describe("FavoriteButton", () => {
it("renders unfavorited state by default", () => {
const mockOnToggle = vi.fn()
renderWithTooltipProvider(<FavoriteButton isFavorite={false} onToggleFavorite={mockOnToggle} />)
const button = screen.getByTestId("favorite-button")
expect(button).toBeInTheDocument()
const icon = button.querySelector("span")
expect(icon).toHaveClass("text-vscode-descriptionForeground")
expect(icon).not.toHaveClass("text-yellow-400")
})
it("renders favorited state correctly", () => {
const mockOnToggle = vi.fn()
renderWithTooltipProvider(<FavoriteButton isFavorite={true} onToggleFavorite={mockOnToggle} />)
const button = screen.getByTestId("favorite-button")
expect(button).toBeInTheDocument()
const icon = button.querySelector("span")
expect(icon).toHaveClass("text-yellow-400")
expect(icon).not.toHaveClass("text-vscode-descriptionForeground")
})
it("calls onToggleFavorite when clicked", () => {
const mockOnToggle = vi.fn()
renderWithTooltipProvider(<FavoriteButton isFavorite={false} onToggleFavorite={mockOnToggle} />)
const button = screen.getByTestId("favorite-button")
fireEvent.click(button)
expect(mockOnToggle).toHaveBeenCalledTimes(1)
})
it("uses correct icon for unfavorited state", () => {
const mockOnToggle = vi.fn()
renderWithTooltipProvider(<FavoriteButton isFavorite={false} onToggleFavorite={mockOnToggle} />)
const icon = screen.getByTestId("favorite-button").querySelector("span")
expect(icon).toHaveClass("codicon-star-empty")
expect(icon).not.toHaveClass("codicon-star-full")
})
it("uses correct icon for favorited state", () => {
const mockOnToggle = vi.fn()
renderWithTooltipProvider(<FavoriteButton isFavorite={true} onToggleFavorite={mockOnToggle} />)
const icon = screen.getByTestId("favorite-button").querySelector("span")
expect(icon).toHaveClass("codicon-star-full")
expect(icon).not.toHaveClass("codicon-star-empty")
})
it("stops event propagation when clicked", () => {
const mockOnToggle = vi.fn()
const mockParentClick = vi.fn()
render(
<TooltipProvider>
<div onClick={mockParentClick}>
<FavoriteButton isFavorite={false} onToggleFavorite={mockOnToggle} />
</div>
</TooltipProvider>,
)
const button = screen.getByTestId("favorite-button")
fireEvent.click(button)
expect(mockOnToggle).toHaveBeenCalledTimes(1)
expect(mockParentClick).not.toHaveBeenCalled()
})
})

View file

@ -0,0 +1,106 @@
import { render, screen, fireEvent } from "@testing-library/react"
import { describe, it, expect, vi } from "vitest"
import { RenameButton } from "../RenameButton"
import { TooltipProvider } from "@/components/ui/tooltip"
const renderWithTooltipProvider = (component: React.ReactElement) => {
return render(<TooltipProvider>{component}</TooltipProvider>)
}
describe("RenameButton", () => {
it("renders rename button by default", () => {
const mockOnRename = vi.fn()
renderWithTooltipProvider(<RenameButton currentName="Test Task" onRename={mockOnRename} />)
const button = screen.getByTestId("rename-button")
expect(button).toBeInTheDocument()
})
it("shows input field when edit mode is activated", () => {
const mockOnRename = vi.fn()
renderWithTooltipProvider(<RenameButton currentName="Test Task" onRename={mockOnRename} />)
const button = screen.getByTestId("rename-button")
fireEvent.click(button)
const input = screen.getByTestId("rename-input")
expect(input).toBeInTheDocument()
expect(input).toHaveValue("Test Task")
})
it("shows save and cancel buttons in edit mode", () => {
const mockOnRename = vi.fn()
renderWithTooltipProvider(<RenameButton currentName="Test Task" onRename={mockOnRename} />)
const button = screen.getByTestId("rename-button")
fireEvent.click(button)
const saveButton = screen.getByTestId("rename-save")
const cancelButton = screen.getByTestId("rename-cancel")
expect(saveButton).toBeInTheDocument()
expect(cancelButton).toBeInTheDocument()
})
it("cancels edit mode when cancel button is clicked", () => {
const mockOnRename = vi.fn()
renderWithTooltipProvider(<RenameButton currentName="Test Task" onRename={mockOnRename} />)
const button = screen.getByTestId("rename-button")
fireEvent.click(button)
const cancelButton = screen.getByTestId("rename-cancel")
fireEvent.click(cancelButton)
expect(mockOnRename).not.toHaveBeenCalled()
expect(screen.getByTestId("rename-button")).toBeInTheDocument()
})
it("does not call onRename when value is unchanged", () => {
const mockOnRename = vi.fn()
renderWithTooltipProvider(<RenameButton currentName="Test Task" onRename={mockOnRename} />)
const button = screen.getByTestId("rename-button")
fireEvent.click(button)
const input = screen.getByTestId("rename-input")
fireEvent.keyDown(input, { key: "Enter" })
expect(mockOnRename).not.toHaveBeenCalled()
})
it("stops event propagation when editing", () => {
const mockOnRename = vi.fn()
const mockParentClick = vi.fn()
render(
<TooltipProvider>
<div onClick={mockParentClick}>
<RenameButton currentName="Test Task" onRename={mockOnRename} />
</div>
</TooltipProvider>,
)
const button = screen.getByTestId("rename-button")
fireEvent.click(button)
const editContainer = screen.getByTestId("rename-input").parentElement
fireEvent.click(editContainer!)
expect(mockParentClick).not.toHaveBeenCalled()
})
it("cancels edit mode when Escape key is pressed", () => {
const mockOnRename = vi.fn()
renderWithTooltipProvider(<RenameButton currentName="Test Task" onRename={mockOnRename} />)
const button = screen.getByTestId("rename-button")
fireEvent.click(button)
const input = screen.getByTestId("rename-input")
fireEvent.keyDown(input, { key: "Escape" })
expect(mockOnRename).not.toHaveBeenCalled()
expect(screen.getByTestId("rename-button")).toBeInTheDocument()
})
})

View file

@ -12,7 +12,14 @@ vi.mock("@/utils/highlight", () => ({
highlightFzfMatch: vi.fn((text) => `<mark>${text}</mark>`),
}))
vi.mock("@/utils/vscode", () => ({
vscode: {
postMessage: vi.fn(),
},
}))
import { useExtensionState } from "@/context/ExtensionStateContext"
import { vscode } from "@/utils/vscode"
const mockUseExtensionState = useExtensionState as ReturnType<typeof vi.fn>
@ -26,6 +33,8 @@ const mockTaskHistory: HistoryItem[] = [
tokensOut: 50,
totalCost: 0.01,
workspace: "/workspace/project1",
isFavorite: false,
customName: undefined,
},
{
id: "task-2",
@ -38,6 +47,8 @@ const mockTaskHistory: HistoryItem[] = [
cacheWrites: 25,
cacheReads: 10,
workspace: "/workspace/project1",
isFavorite: true,
customName: "My Custom Test Task",
},
{
id: "task-3",
@ -48,6 +59,8 @@ const mockTaskHistory: HistoryItem[] = [
tokensOut: 75,
totalCost: 0.05,
workspace: "/workspace/project2",
isFavorite: true,
customName: undefined,
},
]
@ -284,4 +297,113 @@ describe("useTaskSearch", () => {
// When not searching, it should fall back to newest
expect(result.current.sortOption).toBe("mostRelevant")
})
describe("favorites functionality", () => {
it("filters to show only favorites when showFavoritesOnly is true", () => {
const { result } = renderHook(() => useTaskSearch())
act(() => {
result.current.setShowAllWorkspaces(true)
result.current.setShowFavoritesOnly(true)
})
expect(result.current.tasks).toHaveLength(2)
expect(result.current.tasks.every((task) => task.isFavorite)).toBe(true)
expect(result.current.tasks[0].id).toBe("task-2")
expect(result.current.tasks[1].id).toBe("task-3")
})
it("shows all tasks when showFavoritesOnly is false", () => {
const { result } = renderHook(() => useTaskSearch())
act(() => {
result.current.setShowAllWorkspaces(true)
result.current.setShowFavoritesOnly(false)
})
expect(result.current.tasks).toHaveLength(3)
expect(result.current.showFavoritesOnly).toBe(false)
})
it("handles toggle favorite functionality", () => {
const { result } = renderHook(() => useTaskSearch())
act(() => {
result.current.handleToggleFavorite("task-1")
})
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "toggleTaskFavorite",
taskId: "task-1",
})
})
it("handles rename functionality", () => {
const { result } = renderHook(() => useTaskSearch())
act(() => {
result.current.handleRename("task-1", "New Task Name")
})
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "renameTask",
taskId: "task-1",
newName: "New Task Name",
})
})
it("searches in custom names when available", () => {
const { result } = renderHook(() => useTaskSearch())
act(() => {
result.current.setShowAllWorkspaces(true)
result.current.setSearchQuery("Custom")
})
expect(result.current.tasks).toHaveLength(1)
expect(result.current.tasks[0].id).toBe("task-2")
expect((result.current.tasks[0] as any).highlight).toBe("<mark>My Custom Test Task</mark>")
})
it("falls back to original task name when custom name is not available", () => {
const { result } = renderHook(() => useTaskSearch())
act(() => {
result.current.setShowAllWorkspaces(true)
result.current.setSearchQuery("React")
})
expect(result.current.tasks).toHaveLength(1)
expect(result.current.tasks[0].id).toBe("task-1")
expect((result.current.tasks[0] as any).highlight).toBe("<mark>Create a React component</mark>")
})
it("combines favorites filter with search", () => {
const { result } = renderHook(() => useTaskSearch())
act(() => {
result.current.setShowAllWorkspaces(true)
result.current.setShowFavoritesOnly(true)
result.current.setSearchQuery("authentication")
})
expect(result.current.tasks).toHaveLength(1)
expect(result.current.tasks[0].id).toBe("task-3")
expect(result.current.tasks[0].isFavorite).toBe(true)
})
it("combines favorites filter with workspace filter", () => {
const { result } = renderHook(() => useTaskSearch())
act(() => {
result.current.setShowFavoritesOnly(true)
})
// Should only show favorites from current workspace
expect(result.current.tasks).toHaveLength(1)
expect(result.current.tasks[0].id).toBe("task-2")
expect(result.current.tasks[0].workspace).toBe("/workspace/project1")
expect(result.current.tasks[0].isFavorite).toBe(true)
})
})
})

View file

@ -3,6 +3,7 @@ import { Fzf } from "fzf"
import { highlightFzfMatch } from "@/utils/highlight"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { vscode } from "@/utils/vscode"
type SortOption = "newest" | "oldest" | "mostExpensive" | "mostTokens" | "mostRelevant"
@ -12,6 +13,7 @@ export const useTaskSearch = () => {
const [sortOption, setSortOption] = useState<SortOption>("newest")
const [lastNonRelevantSort, setLastNonRelevantSort] = useState<SortOption | null>("newest")
const [showAllWorkspaces, setShowAllWorkspaces] = useState(false)
const [showFavoritesOnly, setShowFavoritesOnly] = useState(false)
useEffect(() => {
if (searchQuery && sortOption !== "mostRelevant" && !lastNonRelevantSort) {
@ -28,12 +30,15 @@ export const useTaskSearch = () => {
if (!showAllWorkspaces) {
tasks = tasks.filter((item) => item.workspace === cwd)
}
if (showFavoritesOnly) {
tasks = tasks.filter((item) => item.isFavorite === true)
}
return tasks
}, [taskHistory, showAllWorkspaces, cwd])
}, [taskHistory, showAllWorkspaces, showFavoritesOnly, cwd])
const fzf = useMemo(() => {
return new Fzf(presentableTasks, {
selector: (item) => item.task,
selector: (item) => item.customName || item.task,
})
}, [presentableTasks])
@ -44,12 +49,13 @@ export const useTaskSearch = () => {
const searchResults = fzf.find(searchQuery)
results = searchResults.map((result) => {
const positions = Array.from(result.positions)
const taskEndIndex = result.item.task.length
const searchText = result.item.customName || result.item.task
const taskEndIndex = searchText.length
return {
...result.item,
highlight: highlightFzfMatch(
result.item.task,
searchText,
positions.filter((p) => p < taskEndIndex),
),
workspace: result.item.workspace,
@ -78,6 +84,21 @@ export const useTaskSearch = () => {
})
}, [presentableTasks, searchQuery, fzf, sortOption])
const handleToggleFavorite = (taskId: string) => {
vscode.postMessage({
type: "toggleTaskFavorite",
taskId,
})
}
const handleRename = (taskId: string, newName: string) => {
vscode.postMessage({
type: "renameTask",
taskId,
newName,
})
}
return {
tasks,
searchQuery,
@ -88,5 +109,9 @@ export const useTaskSearch = () => {
setLastNonRelevantSort,
showAllWorkspaces,
setShowAllWorkspaces,
showFavoritesOnly,
setShowFavoritesOnly,
handleToggleFavorite,
handleRename,
}
}