Merge story-31: View Upcoming Stories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

# Conflicts:
#	frontend/src/api/workflow.ts
#	frontend/src/components/Chat.test.tsx
#	frontend/src/components/Chat.tsx
#	server/src/http/workflow.rs
This commit is contained in:
Dave
2026-02-19 15:54:02 +00:00
11 changed files with 521 additions and 2 deletions

View File

@@ -3,13 +3,14 @@ import Markdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { api, ChatWebSocket } from "../api/client";
import type { ReviewStory } from "../api/workflow";
import type { ReviewStory, UpcomingStory } from "../api/workflow";
import { workflowApi } from "../api/workflow";
import type { Message, ProviderConfig, ToolCall } from "../types";
import { ChatHeader } from "./ChatHeader";
import { GatePanel } from "./GatePanel";
import { ReviewPanel } from "./ReviewPanel";
import { TodoPanel } from "./TodoPanel";
import { UpcomingPanel } from "./UpcomingPanel";
const { useCallback, useEffect, useRef, useState } = React;
@@ -68,6 +69,12 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
const [todoError, setTodoError] = useState<string | null>(null);
const [isTodoLoading, setIsTodoLoading] = useState(false);
const [lastTodoRefresh, setLastTodoRefresh] = useState<Date | null>(null);
const [upcomingStories, setUpcomingStories] = useState<UpcomingStory[]>([]);
const [upcomingError, setUpcomingError] = useState<string | null>(null);
const [isUpcomingLoading, setIsUpcomingLoading] = useState(false);
const [lastUpcomingRefresh, setLastUpcomingRefresh] = useState<Date | null>(
null,
);
const storyId = "26_establish_tdd_workflow_and_gates";
const gateStatusColor = isGateLoading
@@ -375,6 +382,58 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
}
};
useEffect(() => {
let active = true;
setIsUpcomingLoading(true);
setUpcomingError(null);
workflowApi
.getUpcomingStories()
.then((response) => {
if (!active) return;
setUpcomingStories(response.stories);
setLastUpcomingRefresh(new Date());
})
.catch((error) => {
if (!active) return;
const message =
error instanceof Error
? error.message
: "Failed to load upcoming stories.";
setUpcomingError(message);
setUpcomingStories([]);
})
.finally(() => {
if (active) {
setIsUpcomingLoading(false);
}
});
return () => {
active = false;
};
}, []);
const refreshUpcomingStories = async () => {
setIsUpcomingLoading(true);
setUpcomingError(null);
try {
const response = await workflowApi.getUpcomingStories();
setUpcomingStories(response.stories);
setLastUpcomingRefresh(new Date());
} catch (error) {
const message =
error instanceof Error
? error.message
: "Failed to load upcoming stories.";
setUpcomingError(message);
setUpcomingStories([]);
} finally {
setIsUpcomingLoading(false);
}
};
const refreshReviewQueue = async () => {
setIsReviewLoading(true);
setReviewError(null);
@@ -676,6 +735,14 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
lastTodoRefresh={lastTodoRefresh}
onRefresh={refreshTodos}
/>
<UpcomingPanel
stories={upcomingStories}
isLoading={isUpcomingLoading}
error={upcomingError}
lastRefresh={lastUpcomingRefresh}
onRefresh={refreshUpcomingStories}
/>
</div>
</div>