Phase 2: Refactor Tasks cluster to MCP SDK types
Replace ad hoc TaskCardProps data shape with SDK Task type across the Tasks group components and TasksScreen. - TaskCard: accept `task: Task` instead of flat props (taskId, status, method, target, progress, etc.); derive all display fields from the SDK Task object (taskId, status, ttl, createdAt, lastUpdatedAt, statusMessage); remove fields not present on SDK Task (method, target, progress as number, error, completedAt, elapsed) - TaskControls: type statusFilter as `TaskStatus | undefined`; add runtime validation guard on Select onChange - TaskListPanel: accept `Task[]` instead of `TaskCardProps[]`; pass `onCancel(taskId)` through to each TaskCard; use `task.taskId` as key - TasksScreen: accept `Task[]` + `onCancel(taskId)` callback; type internal statusFilter state as `TaskStatus | undefined` - Delete re-export of `TaskStatus` from TaskCard (import directly from SDK where needed) - Update all stories with real SDK `Task` fixtures - Update ConnectedView TasksActive story Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a1052a1b9d
commit
6abb39a5fe
2 changed files with 140 additions and 262 deletions
|
|
@ -1,57 +1,39 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import type { Task } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { fn } from "storybook/test";
|
||||
import { TaskListPanel } from "./TaskListPanel";
|
||||
import type { TaskCardProps } from "../TaskCard/TaskCard";
|
||||
|
||||
const sampleTasks: TaskCardProps[] = [
|
||||
const sampleTasks: Task[] = [
|
||||
{
|
||||
taskId: "d0b22eba71fa36229ce5c4dfadeaa7de",
|
||||
status: "working",
|
||||
method: "tools/call",
|
||||
target: "generate_report",
|
||||
progress: 80,
|
||||
progressDescription: "Synthesizing findings...",
|
||||
startedAt: "3/29/2026, 8:18:20 PM",
|
||||
lastUpdated: "3/29/2026, 8:18:22 PM",
|
||||
ttl: 300000,
|
||||
isListExpanded: true,
|
||||
onCancel: fn(),
|
||||
createdAt: "2026-03-29T20:18:20Z",
|
||||
lastUpdatedAt: "2026-03-29T20:18:22Z",
|
||||
statusMessage: "Synthesizing findings...",
|
||||
},
|
||||
{
|
||||
taskId: "4100b5e0b0ed9cd0023330342d1bf647",
|
||||
status: "input_required",
|
||||
method: "resources/read",
|
||||
target: "file:///data/report.csv",
|
||||
startedAt: "3/29/2026, 8:17:55 PM",
|
||||
lastUpdated: "3/29/2026, 8:17:55 PM",
|
||||
ttl: 300000,
|
||||
isListExpanded: true,
|
||||
onCancel: fn(),
|
||||
createdAt: "2026-03-29T20:17:55Z",
|
||||
lastUpdatedAt: "2026-03-29T20:17:55Z",
|
||||
},
|
||||
{
|
||||
taskId: "d487b49aa39023d907b5a2a5b506cb3",
|
||||
status: "completed",
|
||||
method: "tools/call",
|
||||
target: "generate_summary",
|
||||
startedAt: "3/29/2026, 8:16:47 PM",
|
||||
completedAt: "3/29/2026, 8:16:51 PM",
|
||||
lastUpdated: "3/29/2026, 8:16:51 PM",
|
||||
ttl: 300000,
|
||||
progressDescription: "Generating report...",
|
||||
isListExpanded: true,
|
||||
onCancel: fn(),
|
||||
createdAt: "2026-03-29T20:16:47Z",
|
||||
lastUpdatedAt: "2026-03-29T20:16:51Z",
|
||||
statusMessage: "Report generated successfully",
|
||||
},
|
||||
{
|
||||
taskId: "e6ebffd9cca84ddd1646d3c579a4d453",
|
||||
status: "failed",
|
||||
method: "tools/call",
|
||||
target: "fetch_remote_data",
|
||||
startedAt: "3/29/2026, 8:17:27 PM",
|
||||
lastUpdated: "3/29/2026, 8:17:28 PM",
|
||||
ttl: 300000,
|
||||
error: "Connection refused: upstream server not responding",
|
||||
isListExpanded: true,
|
||||
onCancel: fn(),
|
||||
createdAt: "2026-03-29T20:17:27Z",
|
||||
lastUpdatedAt: "2026-03-29T20:17:28Z",
|
||||
statusMessage: "Connection refused: upstream server not responding",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -61,6 +43,7 @@ const meta: Meta<typeof TaskListPanel> = {
|
|||
args: {
|
||||
tasks: sampleTasks,
|
||||
searchText: "",
|
||||
onCancel: fn(),
|
||||
onClearCompleted: fn(),
|
||||
},
|
||||
};
|
||||
|
|
@ -72,7 +55,7 @@ export const Default: Story = {};
|
|||
|
||||
export const FilteredByStatus: Story = {
|
||||
args: {
|
||||
statusFilter: "running",
|
||||
statusFilter: "working",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -8,14 +8,15 @@ import {
|
|||
Text,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import type { Task, TaskStatus } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { TaskCard } from "../TaskCard/TaskCard";
|
||||
import { ListToggle } from "../../elements/ListToggle/ListToggle";
|
||||
import type { TaskCardProps } from "../TaskCard/TaskCard";
|
||||
|
||||
export interface TaskListPanelProps {
|
||||
tasks: TaskCardProps[];
|
||||
tasks: Task[];
|
||||
searchText: string;
|
||||
statusFilter?: string;
|
||||
statusFilter?: TaskStatus;
|
||||
onCancel: (taskId: string) => void;
|
||||
onClearCompleted: () => void;
|
||||
}
|
||||
|
||||
|
|
@ -37,10 +38,6 @@ const EmptyState = Text.withProps({
|
|||
py: "xl",
|
||||
});
|
||||
|
||||
function taskKey(task: TaskCardProps): string {
|
||||
return task.taskId;
|
||||
}
|
||||
|
||||
function formatActiveTitle(count: number): string {
|
||||
return `Active (${count})`;
|
||||
}
|
||||
|
|
@ -49,20 +46,20 @@ function formatCompletedTitle(count: number): string {
|
|||
return `Completed (${count})`;
|
||||
}
|
||||
|
||||
function isActiveStatus(status: string): boolean {
|
||||
function isActiveStatus(status: TaskStatus): boolean {
|
||||
return status === "working" || status === "input_required";
|
||||
}
|
||||
|
||||
function matchesFilters(
|
||||
task: TaskCardProps,
|
||||
task: Task,
|
||||
searchText: string,
|
||||
statusFilter?: string,
|
||||
statusFilter?: TaskStatus,
|
||||
): boolean {
|
||||
if (statusFilter && task.status !== statusFilter) return false;
|
||||
if (searchText) {
|
||||
const term = searchText.toLowerCase();
|
||||
const searchable =
|
||||
`${task.taskId} ${task.method} ${task.target ?? ""} ${task.status}`.toLowerCase();
|
||||
`${task.taskId} ${task.status} ${task.statusMessage ?? ""}`.toLowerCase();
|
||||
if (!searchable.includes(term)) return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -72,6 +69,7 @@ export function TaskListPanel({
|
|||
tasks,
|
||||
searchText,
|
||||
statusFilter,
|
||||
onCancel,
|
||||
onClearCompleted,
|
||||
}: TaskListPanelProps) {
|
||||
const [compact, setCompact] = useState(false);
|
||||
|
|
@ -115,9 +113,10 @@ export function TaskListPanel({
|
|||
<Title order={5}>{formatActiveTitle(activeTasks.length)}</Title>
|
||||
{activeTasks.map((task) => (
|
||||
<TaskCard
|
||||
key={taskKey(task)}
|
||||
{...task}
|
||||
key={task.taskId}
|
||||
task={task}
|
||||
isListExpanded={!compact}
|
||||
onCancel={() => onCancel(task.taskId)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
|
@ -135,9 +134,10 @@ export function TaskListPanel({
|
|||
</Group>
|
||||
{completedTasks.map((task) => (
|
||||
<TaskCard
|
||||
key={taskKey(task)}
|
||||
{...task}
|
||||
key={task.taskId}
|
||||
task={task}
|
||||
isListExpanded={!compact}
|
||||
onCancel={() => onCancel(task.taskId)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
|
|
|||
Loading…
Reference in a new issue