mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
fix: enhance todo list rendering with robust error handling and validation
- Add comprehensive validation in getLatestTodo function to ensure parsed JSON contains valid todo arrays - Implement normalizedTodos logic in TodoListDisplay component to filter out invalid todo items - Add fallback handling for edge cases where todo data might be malformed or missing - Ensure todo items have required properties (id, content, status) before rendering - Fixes issue #5916 where todo lists would not render properly
This commit is contained in:
parent
8c349767fa
commit
9c02e5dc82
3 changed files with 7714 additions and 27 deletions
7638
package-lock.json
generated
Normal file
7638
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,21 +1,38 @@
|
|||
import { ClineMessage } from "@roo-code/types"
|
||||
export function getLatestTodo(clineMessages: ClineMessage[]) {
|
||||
const todos = clineMessages
|
||||
.filter(
|
||||
(msg) =>
|
||||
(msg.type === "ask" && msg.ask === "tool") || (msg.type === "say" && msg.say === "user_edit_todos"),
|
||||
)
|
||||
.map((msg) => {
|
||||
try {
|
||||
return JSON.parse(msg.text ?? "{}")
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})
|
||||
.filter((item) => item && item.tool === "updateTodoList" && Array.isArray(item.todos))
|
||||
.map((item) => item.todos)
|
||||
.pop()
|
||||
if (todos) {
|
||||
const filteredMessages = clineMessages.filter(
|
||||
(msg) => (msg.type === "ask" && msg.ask === "tool") || (msg.type === "say" && msg.say === "user_edit_todos"),
|
||||
)
|
||||
|
||||
const parsedItems = filteredMessages.map((msg) => {
|
||||
try {
|
||||
const text = msg.text ?? "{}"
|
||||
const parsed = JSON.parse(text)
|
||||
return parsed
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
const todoItems = parsedItems.filter((item) => {
|
||||
if (!item) {
|
||||
return false
|
||||
}
|
||||
|
||||
const hasTool = item.tool === "updateTodoList"
|
||||
const hasTodos = item.todos !== undefined
|
||||
const isArrayTodos = Array.isArray(item.todos)
|
||||
|
||||
return hasTool && hasTodos && isArrayTodos
|
||||
})
|
||||
|
||||
if (todoItems.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const todos = todoItems.map((item) => item.todos).pop()
|
||||
|
||||
if (todos && Array.isArray(todos)) {
|
||||
return todos
|
||||
} else {
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -1,21 +1,49 @@
|
|||
import { useState, useRef, useMemo, useEffect } from "react"
|
||||
|
||||
export function TodoListDisplay({ todos }: { todos: any[] }) {
|
||||
// Normalize todos to ensure we have a valid array
|
||||
const normalizedTodos = useMemo(() => {
|
||||
if (!todos) {
|
||||
return []
|
||||
}
|
||||
if (!Array.isArray(todos)) {
|
||||
// Try to handle case where todos might be a single object
|
||||
if (typeof todos === "object" && "length" in todos) {
|
||||
return Array.from(todos)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// Filter out any invalid todo items
|
||||
const validTodos = todos.filter((todo) => {
|
||||
if (!todo || typeof todo !== "object") {
|
||||
return false
|
||||
}
|
||||
if (!todo.content || typeof todo.content !== "string") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return validTodos
|
||||
}, [todos])
|
||||
|
||||
const [isCollapsed, setIsCollapsed] = useState(true)
|
||||
const ulRef = useRef<HTMLUListElement>(null)
|
||||
const itemRefs = useRef<(HTMLLIElement | null)[]>([])
|
||||
const scrollIndex = useMemo(() => {
|
||||
const inProgressIdx = todos.findIndex((todo: any) => todo.status === "in_progress")
|
||||
const inProgressIdx = normalizedTodos.findIndex((todo: any) => todo.status === "in_progress")
|
||||
if (inProgressIdx !== -1) return inProgressIdx
|
||||
return todos.findIndex((todo: any) => todo.status !== "completed")
|
||||
}, [todos])
|
||||
return normalizedTodos.findIndex((todo: any) => todo.status !== "completed")
|
||||
}, [normalizedTodos])
|
||||
|
||||
// Find the most important todo to display when collapsed
|
||||
const mostImportantTodo = useMemo(() => {
|
||||
const inProgress = todos.find((todo: any) => todo.status === "in_progress")
|
||||
const inProgress = normalizedTodos.find((todo: any) => todo.status === "in_progress")
|
||||
if (inProgress) return inProgress
|
||||
return todos.find((todo: any) => todo.status !== "completed")
|
||||
}, [todos])
|
||||
return normalizedTodos.find((todo: any) => todo.status !== "completed")
|
||||
}, [normalizedTodos])
|
||||
|
||||
useEffect(() => {
|
||||
if (isCollapsed) return
|
||||
if (!ulRef.current) return
|
||||
|
|
@ -29,11 +57,15 @@ export function TodoListDisplay({ todos }: { todos: any[] }) {
|
|||
const scrollTo = targetTop - (ulHeight / 2 - targetHeight / 2)
|
||||
ul.scrollTop = scrollTo
|
||||
}
|
||||
}, [todos, isCollapsed, scrollIndex])
|
||||
if (!Array.isArray(todos) || todos.length === 0) return null
|
||||
}, [normalizedTodos, isCollapsed, scrollIndex])
|
||||
|
||||
const totalCount = todos.length
|
||||
const completedCount = todos.filter((todo: any) => todo.status === "completed").length
|
||||
// Enhanced guard clause
|
||||
if (normalizedTodos.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const totalCount = normalizedTodos.length
|
||||
const completedCount = normalizedTodos.filter((todo: any) => todo.status === "completed").length
|
||||
|
||||
const allCompleted = completedCount === totalCount && totalCount > 0
|
||||
|
||||
|
|
@ -268,7 +300,7 @@ export function TodoListDisplay({ todos }: { todos: any[] }) {
|
|||
overflowY: "auto",
|
||||
padding: "12px 16px",
|
||||
}}>
|
||||
{todos.map((todo: any, idx: number) => {
|
||||
{normalizedTodos.map((todo: any, idx: number) => {
|
||||
let icon
|
||||
if (todo.status === "completed") {
|
||||
icon = (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue