web: Add Tasks to the monitoring sidebar (#1681)

* web: add Tasks to the monitoring sidebar (#1680)

Make Tasks a pinnable panel in the monitoring sidebar alongside
Logs / Protocol / Network / Console, so a live task list can be watched
beside any screen.

- InspectorView: add TASKS_TAB, extend the MonitorTab union / MONITOR_TABS
  / isMonitorTab, and add a monitorScreens[TASKS_TAB] entry built from a
  shared tasksScreenProps object (so the embedded and full-size instances
  can't drift), with the shared column search injected into its ui.search.
  The full-size Tasks screen now uses the same shared props.
- TasksScreen / TaskListPanel: add an embedded prop mirroring
  LoggingScreen/LogStreamPanel — when embedded the screen fills its column
  height and drops the controls sidebar, and TaskListPanel scrolls via
  EmbeddableScrollArea (whose full-size cap matches the old viewport calc,
  so the standalone screen is unchanged).

Tasks is capability-gated: it only appears in the monitor group when the
server advertises the tasks capability (via availableTabs), so no change
is needed to the failed-connect subset.

Closes #1680

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* web: embedded task list ignores the status filter (review #1681)

Mirror the sibling monitor panels: the embedded column exposes only the
shared search box (its status filter lives in the full-size sidebar,
which the embedded view drops), so a stale status filter must not
silently hide tasks with no visible control to clear it. Thread an
`ignoreStatus` flag into TaskListPanel.matchesFilters, set from the
`embedded` prop — matching LogStreamPanel's `ignoreLevels`,
NetworkStreamPanel's `ignoreCategories`, and ProtocolListPanel's
`ignoreDirectionAndMethod` (#1616). Add tests for the ignored-status /
still-applied-search embedded behavior, plus a direct assertion that the
Tasks radio appears in the monitor column.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-07-13 19:11:06 -04:00 • committed by GitHub
commit 590359abc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 123 additions and 45 deletions

View file

@ -1874,6 +1874,9 @@ describe("InspectorView", () => {
expect(
within(header).queryByRole("radio", { name: "Network" }),
).toBeNull();
// Tasks (#1680) joins the monitor group, so it also leaves the header
// (the server advertises the `tasks` capability via allCapabilities).
expect(within(header).queryByRole("radio", { name: "Tasks" })).toBeNull();
// ...and a non-monitor tab still sits in the header.
expect(
within(header).getByRole("radio", { name: "Tools" }),
@ -1884,6 +1887,8 @@ describe("InspectorView", () => {
expect(
screen.getByRole("radio", { name: "Network" }),
).toBeInTheDocument();
// Tasks is available as a column panel.
expect(screen.getByRole("radio", { name: "Tasks" })).toBeInTheDocument();
});
it("returns the monitor group to the header when the column is closed", async () => {

View file

@ -152,6 +152,7 @@ function useListCompact(
}
const SERVERS_TAB = INSPECTOR_SERVERS_TAB;
const TASKS_TAB = "Tasks";
const LOGS_TAB = "Logs";
const PROTOCOL_TAB = "Protocol";
const NETWORK_TAB = "Network";
@ -163,7 +164,7 @@ const ALL_TABS: string[] = [
"Tools",
"Prompts",
"Resources",
"Tasks",
TASKS_TAB,
LOGS_TAB,
PROTOCOL_TAB,
NETWORK_TAB,
@ -174,9 +175,11 @@ const ALL_TABS: string[] = [
// a group action: opening the column removes all *available* monitor tabs from
// the header and hosts them in the column instead. Console (#1621) is the
// stdio server's stderr stream — mutually exclusive with Network (Console shows
// for stdio, Network for HTTP), but both live in the monitor group.
type MonitorTab = "Logs" | "Protocol" | "Network" | "Console";
// for stdio, Network for HTTP), but both live in the monitor group. Tasks
// (#1680) joins the group so a live task list can be watched beside any screen.
type MonitorTab = "Tasks" | "Logs" | "Protocol" | "Network" | "Console";
const MONITOR_TABS: string[] = [
TASKS_TAB,
LOGS_TAB,
PROTOCOL_TAB,
NETWORK_TAB,
@ -185,6 +188,7 @@ const MONITOR_TABS: string[] = [
function isMonitorTab(tab: string): tab is MonitorTab {
return (
tab === TASKS_TAB ||
tab === LOGS_TAB ||
tab === PROTOCOL_TAB ||
tab === NETWORK_TAB ||
@ -1069,12 +1073,28 @@ export function InspectorView({
sortDirection: consoleSort,
onSortChange: setConsoleSort,
};
const tasksScreenProps = {
tasks,
progressByTaskId,
ui: tasksUi,
onUiChange: onTasksUiChange,
onRefresh: onRefreshTasks,
onClearCompleted: onClearCompletedTasks,
onCancel: onCancelTask,
};
// Embedded instances for the pinned column, keyed by tab. MonitoringScreen
// renders only the active one; the rest are unmounted element values. Each
// screen's search field is overridden with the shared column search so it
// filters whichever tab is showing, and carries over as tabs change.
const monitorScreens: Record<string, ReactNode> = {
[TASKS_TAB]: (
<TasksScreen
{...tasksScreenProps}
ui={{ ...tasksUi, search: monitorSearch }}
embedded
/>
),
[LOGS_TAB]: (
<LoggingScreen
{...loggingScreenProps}
@ -1234,16 +1254,8 @@ export function InspectorView({
onCompactChange={setResourcesCompact}
/>
</ScreenStage>
<ScreenStage active={activeTab === "Tasks"}>
<TasksScreen
tasks={tasks}
progressByTaskId={progressByTaskId}
ui={tasksUi}
onUiChange={onTasksUiChange}
onRefresh={onRefreshTasks}
onClearCompleted={onClearCompletedTasks}
onCancel={onCancelTask}
/>
<ScreenStage active={activeTab === TASKS_TAB}>
<TasksScreen {...tasksScreenProps} />
</ScreenStage>
<ScreenStage active={activeTab === LOGS_TAB}>
<LoggingScreen {...loggingScreenProps} />