story-kit: merge 342_story_web_ui_button_to_delete_a_story_from_the_pipeline

This commit is contained in:
Dave
2026-03-20 09:10:56 +00:00
parent 3cfe25f97a
commit 60e1d7bf64
5 changed files with 180 additions and 6 deletions

View File

@@ -373,6 +373,10 @@ export const api = {
launchQaApp(storyId: string) {
return callMcpTool("launch_qa_app", { story_id: storyId });
},
/** Delete a story from the pipeline, stopping any running agent and removing the worktree. */
deleteStory(storyId: string) {
return callMcpTool("delete_story", { story_id: storyId });
},
};
async function callMcpTool(

View File

@@ -213,6 +213,15 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
const [selectedWorkItemId, setSelectedWorkItemId] = useState<string | null>(
null,
);
const handleDeleteItem = React.useCallback(
(item: import("../api/client").PipelineStageItem) => {
api.deleteStory(item.story_id).catch((err: unknown) => {
console.error("Failed to delete story:", err);
});
},
[],
);
const [queuedMessages, setQueuedMessages] = useState<
{ id: string; text: string }[]
>([]);
@@ -1089,6 +1098,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
costs={storyTokenCosts}
onItemClick={(item) => setSelectedWorkItemId(item.story_id)}
onStopAgent={handleStopAgent}
onDeleteItem={handleDeleteItem}
/>
<StagePanel
title="To Merge"
@@ -1096,6 +1106,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
costs={storyTokenCosts}
onItemClick={(item) => setSelectedWorkItemId(item.story_id)}
onStopAgent={handleStopAgent}
onDeleteItem={handleDeleteItem}
/>
<StagePanel
title="QA"
@@ -1103,6 +1114,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
costs={storyTokenCosts}
onItemClick={(item) => setSelectedWorkItemId(item.story_id)}
onStopAgent={handleStopAgent}
onDeleteItem={handleDeleteItem}
/>
<StagePanel
title="Current"
@@ -1113,6 +1125,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
busyAgentNames={busyAgentNames}
onStartAgent={handleStartAgent}
onStopAgent={handleStopAgent}
onDeleteItem={handleDeleteItem}
/>
<StagePanel
title="Backlog"
@@ -1123,6 +1136,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
busyAgentNames={busyAgentNames}
onStartAgent={handleStartAgent}
onStopAgent={handleStopAgent}
onDeleteItem={handleDeleteItem}
/>
<ServerLogsPanel logs={serverLogs} />
</>

View File

@@ -44,6 +44,7 @@ interface StagePanelProps {
emptyMessage?: string;
onItemClick?: (item: PipelineStageItem) => void;
onStopAgent?: (storyId: string, agentName: string) => void;
onDeleteItem?: (item: PipelineStageItem) => void;
/** Map of story_id → total_cost_usd for displaying cost badges. */
costs?: Map<string, number>;
/** Agent roster to populate the start agent dropdown. */
@@ -253,6 +254,7 @@ export function StagePanel({
emptyMessage = "Empty.",
onItemClick,
onStopAgent,
onDeleteItem,
costs,
agentRoster,
busyAgentNames,
@@ -444,9 +446,8 @@ export function StagePanel({
)}
</>
);
return onItemClick ? (
const card = onItemClick ? (
<button
key={`${title}-${item.story_id}`}
type="button"
data-testid={`card-${item.story_id}`}
onClick={() => onItemClick(item)}
@@ -455,12 +456,57 @@ export function StagePanel({
{cardInner}
</button>
) : (
<div data-testid={`card-${item.story_id}`} style={cardStyle}>
{cardInner}
</div>
);
return (
<div
key={`${title}-${item.story_id}`}
data-testid={`card-${item.story_id}`}
style={cardStyle}
style={{ position: "relative" }}
>
{cardInner}
{card}
{onDeleteItem && (
<button
type="button"
data-testid={`delete-btn-${item.story_id}`}
title={`Delete ${item.name ?? item.story_id}`}
onClick={(e) => {
e.stopPropagation();
const label = item.name ?? item.story_id;
if (
window.confirm(
`Delete "${label}"? This cannot be undone.`,
)
) {
onDeleteItem(item);
}
}}
style={{
position: "absolute",
top: "4px",
right: "4px",
background: "transparent",
border: "none",
color: "#555",
cursor: "pointer",
fontSize: "0.85em",
lineHeight: 1,
padding: "2px 4px",
borderRadius: "4px",
}}
onMouseEnter={(e) => {
(e.currentTarget as HTMLButtonElement).style.color =
"#f85149";
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLButtonElement).style.color =
"#555";
}}
>
</button>
)}
</div>
);
})}