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:
@@ -1,5 +1,5 @@
|
||||
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
||||
use crate::io::story_metadata::{StoryMetadata, parse_front_matter};
|
||||
use crate::io::story_metadata::{StoryMetadata, parse_front_matter, parse_unchecked_todos};
|
||||
use crate::workflow::{
|
||||
CoverageReport, StoryTestResults, TestCaseResult, TestStatus,
|
||||
evaluate_acceptance_with_coverage, parse_coverage_json, summarize_results,
|
||||
@@ -87,6 +87,18 @@ struct ReviewListResponse {
|
||||
pub stories: Vec<ReviewStory>,
|
||||
}
|
||||
|
||||
#[derive(Object)]
|
||||
struct StoryTodosResponse {
|
||||
pub story_id: String,
|
||||
pub story_name: Option<String>,
|
||||
pub todos: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Object)]
|
||||
struct TodoListResponse {
|
||||
pub stories: Vec<StoryTodosResponse>,
|
||||
}
|
||||
|
||||
fn load_current_story_metadata(ctx: &AppContext) -> Result<Vec<(String, StoryMetadata)>, String> {
|
||||
let root = ctx.state.get_project_root()?;
|
||||
let current_dir = root.join(".story_kit").join("stories").join("current");
|
||||
@@ -403,6 +415,51 @@ impl WorkflowApi {
|
||||
}))
|
||||
}
|
||||
|
||||
/// List unchecked acceptance criteria (TODOs) for all current stories.
|
||||
#[oai(path = "/workflow/todos", method = "get")]
|
||||
async fn story_todos(&self) -> OpenApiResult<Json<TodoListResponse>> {
|
||||
let root = self.ctx.state.get_project_root().map_err(bad_request)?;
|
||||
let current_dir = root.join(".story_kit").join("stories").join("current");
|
||||
|
||||
if !current_dir.exists() {
|
||||
return Ok(Json(TodoListResponse {
|
||||
stories: Vec::new(),
|
||||
}));
|
||||
}
|
||||
|
||||
let mut stories = Vec::new();
|
||||
let mut entries: Vec<_> = fs::read_dir(¤t_dir)
|
||||
.map_err(|e| bad_request(format!("Failed to read current stories: {e}")))?
|
||||
.filter_map(|e| e.ok())
|
||||
.collect();
|
||||
entries.sort_by_key(|e| e.file_name());
|
||||
|
||||
for entry in entries {
|
||||
let path = entry.path();
|
||||
if path.extension().and_then(|ext| ext.to_str()) != Some("md") {
|
||||
continue;
|
||||
}
|
||||
let story_id = path
|
||||
.file_stem()
|
||||
.and_then(|stem| stem.to_str())
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let contents = fs::read_to_string(&path)
|
||||
.map_err(|e| bad_request(format!("Failed to read {}: {e}", path.display())))?;
|
||||
let story_name = parse_front_matter(&contents)
|
||||
.ok()
|
||||
.and_then(|m| m.name);
|
||||
let todos = parse_unchecked_todos(&contents);
|
||||
stories.push(StoryTodosResponse {
|
||||
story_id,
|
||||
story_name,
|
||||
todos,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Json(TodoListResponse { stories }))
|
||||
}
|
||||
|
||||
/// Ensure a story can be accepted; returns an error when gates fail.
|
||||
#[oai(path = "/workflow/acceptance/ensure", method = "post")]
|
||||
async fn ensure_acceptance(
|
||||
|
||||
Reference in New Issue
Block a user