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

@ -54,6 +54,15 @@ describe("TasksScreen", () => {
);
});
it("drops the controls sidebar when embedded, keeping the list", () => {
renderWithMantine(<TasksScreen {...baseProps} embedded />);
// The list panel still renders (its "Tasks" heading is present)...
expect(screen.getAllByText("Tasks").length).toBeGreaterThan(0);
// ...but the controls sidebar (Refresh + Search) is not rendered.
expect(screen.queryByRole("button", { name: "Refresh" })).toBeNull();
expect(screen.queryByPlaceholderText("Search...")).toBeNull();
});
it("emits the cleared status filter through onUiChange", async () => {
const user = userEvent.setup();
const onUiChange = vi.fn();

View file

@ -12,6 +12,12 @@ export interface TasksScreenProps {
onRefresh: () => void;
onClearCompleted: () => void;
onCancel: (taskId: string) => void;
/**
* True when rendered inside the monitoring sidebar: the screen fills its
* parent's height (instead of the viewport calc) and drops the filter
* sidebar so the narrow column is list-only. Mirrors `LoggingScreen`.
*/
embedded?: boolean;
}
// Search + status filter — controlled by the parent (App) as one object so they
@ -46,23 +52,29 @@ export function TasksScreen({
onRefresh,
onClearCompleted,
onCancel,
embedded = false,
}: TasksScreenProps) {
const { search, statusFilter } = ui;
return (
<ScreenLayout>
<Sidebar>
<SidebarCard>
<TaskControls
searchText={search}
statusFilter={statusFilter}
onSearchChange={(value) => onUiChange({ ...ui, search: value })}
onStatusFilterChange={(value) =>
onUiChange({ ...ui, statusFilter: value })
}
onRefresh={onRefresh}
/>
</SidebarCard>
</Sidebar>
// Embedded fills the monitoring sidebar column (100%); standalone keeps the
// ScreenLayout's default full-screen height. Only override `h` when embedded
// — passing `h={undefined}` would clobber the default (withProps spreads).
<ScreenLayout {...(embedded ? { h: "100%" } : {})}>
{embedded ? null : (
<Sidebar>
<SidebarCard>
<TaskControls
searchText={search}
statusFilter={statusFilter}
onSearchChange={(value) => onUiChange({ ...ui, search: value })}
onStatusFilterChange={(value) =>
onUiChange({ ...ui, statusFilter: value })
}
onRefresh={onRefresh}
/>
</SidebarCard>
</Sidebar>
)}
<TaskListPanel
tasks={tasks}
progressByTaskId={progressByTaskId}
@ -70,6 +82,7 @@ export function TasksScreen({
statusFilter={statusFilter}
onCancel={onCancel}
onClearCompleted={onClearCompleted}
embedded={embedded}
/>
</ScreenLayout>
);