Skip to content

feat(web): add todo list display in prompt toolbar#1290

Merged
RealKai42 merged 4 commits intomainfrom
feat/web-todo-toolbar-display
Mar 2, 2026
Merged

feat(web): add todo list display in prompt toolbar#1290
RealKai42 merged 4 commits intomainfrom
feat/web-todo-toolbar-display

Conversation

@YoungY620
Copy link
Copy Markdown
Collaborator

@YoungY620 YoungY620 commented Feb 28, 2026

Related Issue

Resolve #(issue_number)

Description

  • Add ToolbarTodoTab and ToolbarTodoPanel components that display the current SetTodoList state in the web UI prompt toolbar
  • The "Tasks" tab shows a x/y Tasks progress counter and auto-opens an expandable panel with per-item status icons (pending / in_progress / done)
  • Store (useToolEventsStore) tracks todoItems; stream hook extracts todo data from display blocks in tool results and clears it on session change

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked the related issue, if any.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have run make gen-changelog to update the changelog.
  • I have run make gen-docs to update the user documentation.

Show the current SetTodoList state in the web UI toolbar.
A new "Tasks" tab appears when todo items exist, displaying
each item with status icons (pending/in_progress/done) and
a progress counter (x/y Tasks). The tab auto-closes when
the list is cleared and todo items are reset on session change.

Entire-Checkpoint: 9c89e1470297
Copilot AI review requested due to automatic review settings February 28, 2026 08:43
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +1296 to +1305
if (!isReplay && Array.isArray(return_value.display)) {
const todoBlock = return_value.display.find(
(d: { type: string }) => d.type === "todo",
);
if (todoBlock) {
useToolEventsStore.getState().setTodoItems(
(todoBlock as { type: string; items: TodoItem[] }).items,
);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Todo toolbar state not restored during history replay (reconnect/page refresh)

When the page is refreshed or the WebSocket reconnects, history events are replayed. The !isReplay guard at useSessionStream.ts:1296 causes all replayed ToolResult events (including SetTodoList results) to be skipped for toolbar state extraction. This means the toolbar's "Tasks" tab disappears after a reconnection even though the chat messages correctly show the todo display blocks from the replay.

Root cause and impact

The code follows the same !isReplay pattern used by handleToolResult at web/src/features/tool/store.ts:48, which tracks newFiles — a notification-style badge that intentionally shouldn't re-trigger on replay. However, todoItems represents persistent UI state (the current todo list), not a transient notification. The last SetTodoList call's display block contains the full current todo list, and skipping it during replay loses that state.

Scenario:

  1. User triggers SetTodoList → toolbar shows "2/5 Tasks" tab
  2. User refreshes the page (or WebSocket reconnects)
  3. History replay runs — all events have isReplay = true
  4. The if (!isReplay && ...) check skips todo extraction
  5. Toolbar shows no "Tasks" tab, but the chat message still renders the todo display block

Impact: Inconsistent UI after any reconnection — chat shows the todo list in messages but the toolbar indicator is missing until the next SetTodoList tool call.

Prompt for agents
In web/src/hooks/useSessionStream.ts around line 1296, the todo extraction from display blocks should also run during replay to restore the toolbar state. However, during replay you only want the LAST todo state (not intermediate ones). One approach: remove the `!isReplay` guard so todo items are always extracted. Since each SetTodoList call replaces the full list via `setTodoItems`, the last replayed event will set the correct final state. Change line 1296 from `if (!isReplay && Array.isArray(return_value.display))` to `if (Array.isArray(return_value.display))`. This aligns with the fact that todoItems represents persistent state rather than transient notifications.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a prompt-toolbar “Tasks” UI that surfaces the current SetTodoList state in the web frontend, backed by a Zustand store updated from tool-result display blocks.

Changes:

  • Introduce ToolbarTodoTab / ToolbarTodoPanel to render task progress and per-item status.
  • Track todoItems in useToolEventsStore and update/reset it from useSessionStream.
  • Document the feature in English/Chinese release notes and root CHANGELOG.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
web/src/hooks/useSessionStream.ts Extracts todo display blocks from tool results and clears todos on session switch.
web/src/features/tool/store.ts Adds todoItems state + setters/clearer to the tool events store.
web/src/features/chat/components/prompt-toolbar/toolbar-todo.tsx New tab/panel components for rendering todo progress and list.
web/src/features/chat/components/prompt-toolbar/index.tsx Wires the todo tab/panel into the prompt toolbar and auto-closes when empty.
docs/zh/release-notes/changelog.md Adds unreleased note for the new toolbar todo display (ZH).
docs/en/release-notes/changelog.md Adds unreleased note for the new toolbar todo display (EN).
CHANGELOG.md Adds unreleased note for the new toolbar todo display.
Comments suppressed due to low confidence (1)

web/src/features/chat/components/prompt-toolbar/index.tsx:65

  • PR description mentions the todo panel auto-opens when the todo list becomes active, but the toolbar only auto-opens the queue tab. Consider adding an effect similar to the queue auto-open that opens the todo tab when todoItems transitions from empty to non-empty (and keep the existing auto-close behavior when it becomes empty).
  // Auto-open queue tab when first item is added
  useEffect(() => {
    if (prevQueueLenRef.current === 0 && queue.length > 0) {
      setActiveTab("queue");
    }
    prevQueueLenRef.current = queue.length;
  }, [queue.length]);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1301 to +1303
useToolEventsStore.getState().setTodoItems(
(todoBlock as { type: string; items: TodoItem[] }).items,
);
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToolResult.return_value.display items are typed (and handled elsewhere) as display blocks with a data payload, but this code assumes the todo list lives directly on the block as .items. If the backend sends { type: "todo", data: { items: [...] } } (or any variant without a top-level items), this will set todoItems to undefined and downstream code like todoItems.length will throw. Extract items from (todoBlock as any).items ?? (todoBlock as any).data?.items, validate with Array.isArray, and only then update the store (optionally normalize status values).

Suggested change
useToolEventsStore.getState().setTodoItems(
(todoBlock as { type: string; items: TodoItem[] }).items,
);
const rawItems =
(todoBlock as any).items ?? (todoBlock as any).data?.items;
if (Array.isArray(rawItems)) {
useToolEventsStore.getState().setTodoItems(
rawItems as TodoItem[],
);
}

Copilot uses AI. Check for mistakes.
}

// Extract todo list from display blocks
if (!isReplay && Array.isArray(return_value.display)) {
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The todo extraction is skipped during history replay (!isReplay). That means when a user reconnects to an existing session (or refreshes the page), the toolbar won't reflect the current SetTodoList state until a new todo update happens in the live stream. If the goal is to show the current todo state for the session, consider applying the extraction during replay as well (it will naturally converge to the latest todo block by the end of the replay), while keeping any “auto-open” behavior gated to non-replay if needed.

Suggested change
if (!isReplay && Array.isArray(return_value.display)) {
if (Array.isArray(return_value.display)) {

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +26
// eslint-disable-next-line react/no-array-index-key
key={i}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToolbarTodoPanel uses the array index as the React key. If the todo list can reorder (or items can be inserted/removed), this can cause incorrect row/state reuse. Prefer a more stable key (e.g., a backend-provided id, or a combination like title + status, possibly with the index only as a last-resort disambiguator).

Suggested change
// eslint-disable-next-line react/no-array-index-key
key={i}
key={`${item.title}-${item.status}-${i}`}

Copilot uses AI. Check for mistakes.
- Cast todoBlock through unknown to satisfy TS2352 type overlap requirement
- Use item.title as React key instead of array index to fix noArrayIndexKey lint error

Entire-Checkpoint: 28db10720a96
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines 68 to +72
useEffect(() => {
if (activeTab === "queue" && !hasQueue) setActiveTab(null);
if (activeTab === "changes" && !hasChanges) setActiveTab(null);
}, [activeTab, hasQueue, hasChanges]);
if (activeTab === "todo" && !hasTodo) setActiveTab(null);
}, [activeTab, hasQueue, hasChanges, hasTodo]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Todo tab missing auto-open behavior described in PR

The PR description states the Tasks tab "auto-opens an expandable panel with per-item status icons", but the implementation has no auto-open logic for the todo tab. The queue tab has auto-open behavior via prevQueueLenRef tracking (lines 59-65 of index.tsx), but the todo tab lacks equivalent logic.

Root Cause

When the first queue item is added, the queue tab auto-opens:

useEffect(() => {
  if (prevQueueLenRef.current === 0 && queue.length > 0) {
    setActiveTab("queue");
  }
  prevQueueLenRef.current = queue.length;
}, [queue.length]);

There is no corresponding effect for the todo tab. When todoItems transitions from empty to non-empty (i.e., the first SetTodoList tool result arrives), the tab appears in the toolbar but does not automatically expand its panel. The user must manually click the tab to see the todo items.

Impact: The todo panel never auto-opens, contradicting the stated feature behavior. Users won't see todo progress unless they notice and click the small tab button.

(Refers to lines 67-72)

Prompt for agents
In web/src/features/chat/components/prompt-toolbar/index.tsx, add auto-open logic for the todo tab similar to the queue tab. Add a prevTodoLenRef = useRef(0) alongside prevQueueLenRef (around line 50), then add a useEffect that sets activeTab to 'todo' when todoItems transitions from 0 to >0:

const prevTodoLenRef = useRef(0);

useEffect(() => {
  if (prevTodoLenRef.current === 0 && todoItems.length > 0) {
    setActiveTab("todo");
  }
  prevTodoLenRef.current = todoItems.length;
}, [todoItems.length]);

Place this after the existing queue auto-open effect (after line 65).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Prevents duplicate React key warnings when multiple todo items share the same title, consistent with the pattern in display-content.tsx TodoContent.

Entire-Checkpoint: f598a8c28cfd
@RealKai42 RealKai42 merged commit 0d8e6ca into main Mar 2, 2026
26 of 27 checks passed
@RealKai42 RealKai42 deleted the feat/web-todo-toolbar-display branch March 2, 2026 03:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants