Skip to content

Conversation

@DeJeune
Copy link
Collaborator

@DeJeune DeJeune commented Jan 14, 2026

What this PR does

UI Enhancement: Pin TodoWriteTool above inputbar

Before this PR:

  • TodoWriteTool was rendered in the message stream along with other tool outputs
  • Users had to scroll through messages to see current task progress

After this PR:

  • TodoWriteTool is pinned in a fixed panel above the inputbar
  • Only shows incomplete todos (pending and in_progress status)
  • Panel automatically disappears when all todos are completed
  • TodoWrite is completely removed from message stream for cleaner UI
image image

Bug Fix: Data migration for block reference inconsistency

Problem discovered:
When clicking the X button to close PinnedTodoPanel, the deletion was not persisted after app restart. Investigation revealed a deeper data inconsistency issue.

Root cause:
AgentPersistedMessage has two related fields:

  • message.blocks: string[] - ID references
  • blocks: MessageBlock[] - actual block data

When blocks are added via AgentMessageDataSource.updateBlocks(), the block objects are added to the blocks array but their IDs are NOT added to message.blocks. This causes:

  1. Block deletion to fail (removeBlockFromMessage only checked message.blocks)
  2. TodoWrite blocks to reappear after restart (deletion wasn't finding the block)

Solution:

  1. Data migration (runs once on app startup):

    • Scans all session_messages in agents.db
    • Finds blocks in blocks array whose IDs are missing from message.blocks
    • Adds missing IDs to message.blocks
    • Tracked in migrations table (version 10001) to prevent re-runs
    • Uses Drizzle ORM, integrated with existing migration system
  2. Fix removeBlockFromMessage():

    • Now removes from both message.blocks AND blocks array
    • Works even if ID only exists in one of them
  3. Fix PinnedTodoPanel close behavior:

    • Now deletes ALL TodoWrite blocks for the session (not just the latest)
    • Prevents "rollback" behavior where previous TodoWrite snapshot appears

Why we need it and why it was done in this way

The following tradeoffs were made:

  • TodoWrite is completely hidden from message stream (not just during pending/invoking) to avoid duplicate display
  • Only the latest TodoWrite block with incomplete todos is shown to keep the panel focused
  • Data migration runs automatically on startup with version tracking to ensure it only runs once

The following alternatives were considered:

  • Showing TodoWrite in both panel and message stream - rejected for cleaner UX
  • Showing all TodoWrite blocks - rejected to keep panel focused on current progress
  • Manual migration trigger - rejected in favor of automatic migration with version tracking

Breaking changes

None. This is a UI enhancement with a backward-compatible data migration that doesn't change any APIs.

Special notes for your reviewer

UI Changes

  • The implementation follows the same pattern as the existing QuickPanel positioning
  • Uses Redux selector (selectLatestTodoWriteBlock) to efficiently find the latest TodoWrite block with incomplete todos
  • Panel has collapsible UI with progress indicator (e.g., "2/5 tasks completed")
  • Supports i18n for en-us, zh-cn, zh-tw (other languages have placeholder translations)

Data Migration

  • New files:
    • DataMigrationService.ts - Manages data migrations with version tracking
    • migrateBlockReferences.ts - The actual migration logic
    • migrateBlockReferences.utils.ts - Pure utility functions (testable)
    • migrateBlockReferences.test.ts - Unit tests
  • Migration integrates with existing Drizzle migration system
  • Uses version 10001 (data migrations use 10000+ to avoid conflict with schema migrations)
  • Logs migration results: { totalMessages, messagesFixed, blockReferencesAdded, errors }

Checklist

  • PR: The PR description is expressive enough and will help future contributors
  • Code: Write code that humans can understand and Keep it simple
  • Refactor: You have left the code cleaner than you found it (Boy Scout Rule)
  • Upgrade: Impact of this change on upgrade flows was considered and addressed if required
  • Documentation: A user-guide update was considered and is present (link) or not required

Release note

feat: TodoWriteTool is now pinned above the inputbar in Agent Session, showing only incomplete tasks with progress indicator
fix: Fixed block deletion not persisting after app restart by adding data migration to fix block reference inconsistency

@DeJeune DeJeune requested a review from 0xfullex as a code owner January 14, 2026 12:54
@DeJeune DeJeune marked this pull request as draft January 14, 2026 13:34
The selector was showing TodoWrite tasks from all sessions.
Now it only shows todos from the current session by filtering
blocks based on the topicId.

Changes:
- Rename selectLatestTodoWriteBlock to selectLatestTodoWriteBlockForTopic
- Add topicId parameter to selector, hook, and component
- Pass sessionTopicId from AgentSessionInputbar to PinnedTodoPanel

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@DeJeune DeJeune marked this pull request as ready for review January 14, 2026 16:56
DeJeune and others added 2 commits January 15, 2026 01:17
Display all todos (completed, in_progress, pending) in the panel
so users can see the full progress. Completed tasks are shown with
strikethrough text and reduced opacity.

Changes:
- Update useActiveTodos to return all todos instead of just incomplete
- Add visual styling for completed todos (strikethrough, opacity)
- Add i18n translation for "completed" status

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@DeJeune DeJeune added this to the v1.7.14 milestone Jan 19, 2026
@GeorgeDong32

This comment was marked as resolved.

@DeJeune DeJeune requested a review from kangfenmao January 23, 2026 06:50
@DeJeune
Copy link
Collaborator Author

DeJeune commented Jan 23, 2026

Note

This issue/comment/review was translated by Claude.

Fixed.


Original Content

The layout is a bit unstable here, but it gets better when I switch topics.

修复了

@DeJeune
Copy link
Collaborator Author

DeJeune commented Jan 23, 2026

Note

This issue/comment/review was translated by Claude.

Some minor style adjustments will be made in #12540


Original Content

还有些样式微调放到 #12540 进行

DeJeune and others added 3 commits January 23, 2026 15:43
Problem:
When blocks are added via AgentMessageDataSource.updateBlocks(), the block
objects are added to the `blocks` array but their IDs are NOT added to
`message.blocks`. This causes:
1. Block deletion to fail (removeBlockFromMessage checks message.blocks)
2. PinnedTodoPanel close button not persisting after app restart

Root cause:
- AgentPersistedMessage has two related fields:
  - message.blocks: string[] (ID references)
  - blocks: MessageBlock[] (actual block data)
- updateBlocks() only updates `blocks` array, not `message.blocks`

Solution:
1. Data migration (runs once on app startup):
   - Scans all session_messages
   - Finds blocks in `blocks` array whose IDs are missing from `message.blocks`
   - Adds missing IDs to `message.blocks`
   - Tracked in migrations table (version 10001) to prevent re-runs

2. Fix removeBlockFromMessage():
   - Now removes from both `message.blocks` AND `blocks` array
   - Works even if ID only exists in one of them

3. Fix PinnedTodoPanel close:
   - Now deletes ALL TodoWrite blocks for the session
   - Prevents "rollback" behavior where previous TodoWrite appears

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@kangfenmao

This comment was marked as resolved.

@DeJeune DeJeune modified the milestones: v1.7.14, v1.7.16 Jan 27, 2026
DeJeune and others added 3 commits January 28, 2026 02:54
TodoWrite tools are always rendered in PinnedTodoPanel, making the
dedicated component in MessageAgentTools dead code. Remove the component
and related test cases.

Amp-Thread-ID: https://ampcode.com/threads/T-019c00bf-c223-726e-9165-794500c6c934
Co-authored-by: Amp <amp@ampcode.com>
@DeJeune DeJeune requested a review from GeorgeDong32 January 30, 2026 04:46
Copy link
Collaborator

@GeorgeDong32 GeorgeDong32 left a comment

Choose a reason for hiding this comment

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

LGTM! The implementation is clean and the data migration strategy is sound.

One suggestion for optimization in src/renderer/src/store/messageBlock.ts:

The selectLatestTodoWriteBlockForTopic selector currently iterates over messageBlocksSelectors.selectAll (all blocks in the store). If the store grows large (many loaded sessions), this O(N) scan might become expensive.

Since we already have messageIdsByTopic, it would be more performant to iterate the topic's messages in reverse and look up their blocks.

Proposed optimization:

export const selectLatestTodoWriteBlockForTopic = createSelector(
  [
    (state: RootState) => state.messages.entities,
    (state: RootState) => state.messageBlocks.entities,
    (state: RootState) => state.messages.messageIdsByTopic,
    (_state: RootState, topicId: string) => topicId
  ],
  (messageEntities, blockEntities, messageIdsByTopic, topicId): ToolMessageBlock | undefined => {
    const topicMessageIds = messageIdsByTopic[topicId]
    if (!topicMessageIds?.length) return undefined

    // Iterate messages in reverse (newest first)
    for (let i = topicMessageIds.length - 1; i >= 0; i--) {
      const messageId = topicMessageIds[i]
      const message = messageEntities[messageId]
      
      if (!message?.blocks?.length) continue

      // Check blocks of the message
      for (const blockId of message.blocks) {
        const block = blockEntities[blockId]
        // ... check if it is TodoWrite and has incomplete todos ...
         if (
          block?.type === MessageBlockType.TOOL && 
          (block.metadata?.rawMcpToolResponse as NormalToolResponse)?.tool?.name === 'TodoWrite'
        ) {
           // ... logic ...
           return block as ToolMessageBlock
        }
      }
    }
    return undefined
  }
)

This ensures we only scan relevant messages/blocks and respects the message order strictly.

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.

5 participants