Story 31: View Upcoming Stories

Add GET /workflow/upcoming endpoint that reads .story_kit/stories/upcoming/
and returns story IDs with names parsed from frontmatter. Add UpcomingPanel
component wired into Chat view with loading, error, empty, and list states.

12 new tests (3 backend, 9 frontend) all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dave
2026-02-19 15:51:12 +00:00
parent 644644d5b3
commit 939387104b
12 changed files with 505 additions and 18 deletions

View File

@@ -35,6 +35,7 @@ vi.mock("../api/workflow", () => {
ensureAcceptance: vi.fn(),
recordCoverage: vi.fn(),
collectCoverage: vi.fn(),
getUpcomingStories: vi.fn(),
},
};
});
@@ -54,6 +55,7 @@ const mockedWorkflow = {
getReviewQueue: vi.mocked(workflowApi.getReviewQueue),
getReviewQueueAll: vi.mocked(workflowApi.getReviewQueueAll),
ensureAcceptance: vi.mocked(workflowApi.ensureAcceptance),
getUpcomingStories: vi.mocked(workflowApi.getUpcomingStories),
};
describe("Chat review panel", () => {
@@ -75,6 +77,7 @@ describe("Chat review panel", () => {
});
mockedWorkflow.getReviewQueueAll.mockResolvedValue({ stories: [] });
mockedWorkflow.ensureAcceptance.mockResolvedValue(true);
mockedWorkflow.getUpcomingStories.mockResolvedValue({ stories: [] });
});
it("shows an empty review queue state", async () => {
@@ -466,6 +469,23 @@ describe("Chat review panel", () => {
expect(await screen.findByText(/Coverage: 92\.0%/)).toBeInTheDocument();
});
it("fetches upcoming stories on mount and renders panel", async () => {
mockedWorkflow.getUpcomingStories.mockResolvedValueOnce({
stories: [
{ story_id: "31_view_upcoming", name: "View Upcoming Stories" },
{ story_id: "32_worktree", name: null },
],
});
render(<Chat projectPath="/tmp/project" onCloseProject={vi.fn()} />);
expect(await screen.findByText("Upcoming Stories")).toBeInTheDocument();
expect(
await screen.findByText("View Upcoming Stories"),
).toBeInTheDocument();
expect(await screen.findByText("32_worktree")).toBeInTheDocument();
});
it("collect coverage button triggers collection and refreshes gate", async () => {
const mockedCollectCoverage = vi.mocked(workflowApi.collectCoverage);
mockedCollectCoverage.mockResolvedValueOnce({