Story 28: Show remaining test TODOs in the UI
Add TodoPanel that displays unchecked acceptance criteria from current story files. Backend parses `- [ ]` lines from markdown, frontend shows them in a panel with refresh. Includes 4 Rust unit tests, 3 Vitest tests, 3 Playwright E2E tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import type { Message, ProviderConfig, ToolCall } from "../types";
|
||||
import { ChatHeader } from "./ChatHeader";
|
||||
import { GatePanel } from "./GatePanel";
|
||||
import { ReviewPanel } from "./ReviewPanel";
|
||||
import { TodoPanel } from "./TodoPanel";
|
||||
|
||||
const { useCallback, useEffect, useRef, useState } = React;
|
||||
|
||||
@@ -61,6 +62,12 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
const [lastGateRefresh, setLastGateRefresh] = useState<Date | null>(null);
|
||||
const [isCollectingCoverage, setIsCollectingCoverage] = useState(false);
|
||||
const [coverageError, setCoverageError] = useState<string | null>(null);
|
||||
const [storyTodos, setStoryTodos] = useState<
|
||||
{ storyId: string; storyName: string | null; items: string[] }[]
|
||||
>([]);
|
||||
const [todoError, setTodoError] = useState<string | null>(null);
|
||||
const [isTodoLoading, setIsTodoLoading] = useState(false);
|
||||
const [lastTodoRefresh, setLastTodoRefresh] = useState<Date | null>(null);
|
||||
|
||||
const storyId = "26_establish_tdd_workflow_and_gates";
|
||||
const gateStatusColor = isGateLoading
|
||||
@@ -255,6 +262,68 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
setIsTodoLoading(true);
|
||||
setTodoError(null);
|
||||
|
||||
workflowApi
|
||||
.getStoryTodos()
|
||||
.then((response) => {
|
||||
if (!active) return;
|
||||
setStoryTodos(
|
||||
response.stories.map((s) => ({
|
||||
storyId: s.story_id,
|
||||
storyName: s.story_name,
|
||||
items: s.todos,
|
||||
})),
|
||||
);
|
||||
setLastTodoRefresh(new Date());
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!active) return;
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to load story TODOs.";
|
||||
setTodoError(message);
|
||||
setStoryTodos([]);
|
||||
})
|
||||
.finally(() => {
|
||||
if (active) {
|
||||
setIsTodoLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const refreshTodos = async () => {
|
||||
setIsTodoLoading(true);
|
||||
setTodoError(null);
|
||||
|
||||
try {
|
||||
const response = await workflowApi.getStoryTodos();
|
||||
setStoryTodos(
|
||||
response.stories.map((s) => ({
|
||||
storyId: s.story_id,
|
||||
storyName: s.story_name,
|
||||
items: s.todos,
|
||||
})),
|
||||
);
|
||||
setLastTodoRefresh(new Date());
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Failed to load story TODOs.";
|
||||
setTodoError(message);
|
||||
setStoryTodos([]);
|
||||
} finally {
|
||||
setIsTodoLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshGateState = async (targetStoryId: string = storyId) => {
|
||||
setIsGateLoading(true);
|
||||
setGateError(null);
|
||||
@@ -593,6 +662,14 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
onCollectCoverage={handleCollectCoverage}
|
||||
isCollectingCoverage={isCollectingCoverage}
|
||||
/>
|
||||
|
||||
<TodoPanel
|
||||
todos={storyTodos}
|
||||
isTodoLoading={isTodoLoading}
|
||||
todoError={todoError}
|
||||
lastTodoRefresh={lastTodoRefresh}
|
||||
onRefresh={refreshTodos}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user