diff --git a/apps/web/src/components/TodoPanel.tsx b/apps/web/src/components/TodoPanel.tsx index ac4bf988b857..930736d2dea7 100644 --- a/apps/web/src/components/TodoPanel.tsx +++ b/apps/web/src/components/TodoPanel.tsx @@ -233,7 +233,13 @@ export function TodoPanel() { {loading &&
Loading...
} {error &&
Failed to load todos
} {!loading && !error && filteredCategories.length === 0 && doneItems.length === 0 && ( -
No todos yet
+
+

+ No categories yet. +
+ Click + to create one. +

+
)} {filteredCategories.map((category) => ( @@ -378,12 +384,14 @@ function SortableCategoryHeader({ function SortableTodoItem({ item, + categoryColor, categoryJiraLink, onCycleItem, onSelectItem, mutate, }: { item: TodoItem; + categoryColor: string; categoryJiraLink: string | undefined; onCycleItem: (itemId: string) => void; onSelectItem: (itemId: string) => void; @@ -453,7 +461,7 @@ function SortableTodoItem({ return (
@@ -540,9 +548,8 @@ function TodoCategoryRow({ const commitAdd = () => { const trimmed = addValue.trim(); - if (trimmed) { - mutate([{ type: "createItem", categoryId: category.id, title: trimmed }]); - } + if (!trimmed) return; + mutate([{ type: "createItem", categoryId: category.id, title: trimmed }]); setAdding(false); setAddValue(""); }; @@ -682,6 +689,7 @@ function TodoCategoryRow({ { const created = next.items.find((i) => i.categoryId === "cat-1"); expect(created!.sortOrder).toBe(0); }); + + it("rejects empty title and returns unchanged state", () => { + const state = loadTodos(sampleCategories, sampleItems); + const next = createItem(state, "cat-1", ""); + + expect(next).toBe(state); + expect(next.items).toHaveLength(2); + }); + + it("rejects whitespace-only title and returns unchanged state", () => { + const state = loadTodos(sampleCategories, sampleItems); + const next = createItem(state, "cat-1", " "); + + expect(next).toBe(state); + expect(next.items).toHaveLength(2); + }); + + it("rejects whitespace-only title through applyMutation", () => { + const state = loadTodos(sampleCategories, sampleItems); + const next = applyMutation(state, { + type: "createItem", + categoryId: "cat-1", + title: " ", + }); + + expect(next).toBe(state); + expect(next.items).toHaveLength(2); + }); }); describe("updateDescription", () => { diff --git a/packages/shared/src/todoStore.ts b/packages/shared/src/todoStore.ts index 91cb9d9faf35..6abbecc74903 100644 --- a/packages/shared/src/todoStore.ts +++ b/packages/shared/src/todoStore.ts @@ -114,6 +114,8 @@ export function deleteCategory( } export function createItem(state: TodoState, categoryId: string, title: string): TodoState { + if (!title.trim()) return state; + // @effect-diagnostics-next-line globalDate:off const now = new Date().toISOString(); const categoryItems = state.items.filter((item) => item.categoryId === categoryId);