Skip to content

fix(cli): hide completed sticky todos#4635

Merged
wenshao merged 2 commits into
QwenLM:mainfrom
he-yufeng:fix/hide-completed-sticky-todos
May 31, 2026
Merged

fix(cli): hide completed sticky todos#4635
wenshao merged 2 commits into
QwenLM:mainfrom
he-yufeng:fix/hide-completed-sticky-todos

Conversation

@he-yufeng

Copy link
Copy Markdown
Contributor

Summary

  • Hide completed todo items from the sticky Current tasks panel.
  • Keep original task numbers for the remaining visible items.
  • Hide the sticky panel when the latest todo snapshot contains only completed tasks.

Fixes #4631

Test Plan

  • npm run test --workspace=packages/cli -- StickyTodoList.test.tsx
  • npm run typecheck --workspace=packages/cli
  • npx eslint packages/cli/src/ui/components/StickyTodoList.tsx packages/cli/src/ui/components/StickyTodoList.test.tsx --max-warnings 0
  • npx prettier --check packages/cli/src/ui/components/StickyTodoList.tsx packages/cli/src/ui/components/StickyTodoList.test.tsx
  • npm run build --workspace=packages/cli
  • git diff --check

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] getStickyTodosLayoutKey in packages/cli/src/ui/utils/todoSnapshot.ts also needs to be updated to mirror the new completed-todo filtering. Currently it serializes the raw todos array (without sorting or filtering) into the layout key used as a useLayoutEffect dependency in AppContainer.tsx for footer height measurement. When a todo transitions to completed, the rendered sticky panel shrinks but the layout key stays the same, so controlsHeight goes stale — causing a transient scroll-region miscalculation until the next unrelated trigger (buffer change, terminal resize) re-measures.

Suggested fix: apply the same getOrderedStickyTodos + .filter(status !== 'completed') in getStickyTodosLayoutKey, and include todo.status in the serialized key so status transitions trigger re-measurement.

— qwen3.7-max via Qwen Code /review

const orderedTodos = useMemo(() => getOrderedStickyTodos(todos), [todos]);
const orderedOpenTodos = useMemo(
() =>
getOrderedStickyTodos(todos).filter(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] This filter introduces a cross-file consistency gap: getStickyTodosLayoutKey in todoSnapshot.ts still computes its layout key from the raw, unsorted, unfiltered todos array. The layout key is used as a useLayoutEffect dependency in AppContainer.tsx for footer height re-measurement. When a todo transitions to completed, the component correctly hides it, but the layout key doesn't change — leaving controlsHeight stale by the height delta of the hidden row(s). This manifests as a transient scroll-region glitch that self-corrects on the next terminal event.

Consider updating getStickyTodosLayoutKey to apply the same getOrderedStickyTodos(todos).filter(t => t.status !== 'completed') and include todo.status in the serialized entries. Also worth cleaning up: strikethrough={todo.status === 'completed'} and STATUS_ICONS.completed are now dead code since completed items never reach the render loop.

— qwen3.7-max via Qwen Code /review

@he-yufeng

Copy link
Copy Markdown
Contributor Author

Addressed in 9f00908ac.

The layout key now mirrors the rendered sticky todo set:

  • orders todos through getOrderedStickyTodos
  • filters out completed items before slicing visible rows
  • includes status in the serialized visible-todo key so status transitions trigger re-measurement
  • keeps the hidden-count key based on the rendered open todo list

Validation run locally:

  • npm exec -- vitest run packages/cli/src/ui/utils/todoSnapshot.test.ts packages/cli/src/ui/components/StickyTodoList.test.tsx -> 21 passed
  • npm exec -- eslint packages/cli/src/ui/utils/todoSnapshot.ts packages/cli/src/ui/utils/todoSnapshot.test.ts packages/cli/src/ui/components/StickyTodoList.test.tsx
  • npm run build --workspace=@qwen-code/channel-feishu
  • npm run build --workspace=@qwen-code/channel-base
  • npm run typecheck --workspace=packages/cli
  • git diff --check

No package-lock change is included.

@wenshao wenshao requested a review from tanzhenxin May 31, 2026 08:00

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

R2 re-review after the getStickyTodosLayoutKey update — that suggestion is fully addressed. This round found 0 Critical + 5 Suggestions (2 inline below, 3 additional):

  • Dead code (not in diff): STATUS_ICONS.completed (L31) and strikethrough={todo.status === 'completed'} (L108) are now unreachable since completed todos are filtered before the render loop. Consider removing both.
  • getStickyTodosRenderKey mismatch (L134-140): The memo comparator still uses getStickyTodosRenderKey which serializes ALL todos including completed. Content changes to completed todos trigger a re-render with no visible effect. Consider filtering completed todos in the render key too.

— qwen3.7-max via Qwen Code /review

});

it('hides the sticky panel when every task is completed', () => {
const todos: TodoItem[] = [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] This assertion (not.toContain('... and 9 more')) only excludes the old incorrect count. If a future refactor accidentally produces ... and 8 more or ... and 3 more, this test would still pass.

A broader assertion would catch more regressions:

expect(output).not.toContain('... and');

This confirms the hidden-item summary is entirely absent (since all 9 filtered-out completed items are excluded from the count).

— qwen3.7-max via Qwen Code /review

const visibleTodos = todos.slice(0, visibleTodoCount);
const hasHiddenTodos = todos.length > visibleTodos.length;
const orderedOpenTodos = getOrderedStickyTodos(todos).filter(
(todo) => todo.status !== 'completed',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] This getOrderedStickyTodos(todos).filter((todo) => todo.status !== 'completed') is identical to the one in StickyTodoList.tsx:48-52. If the filter predicate changes in one place but not the other, the layout key will diverge from what the component actually renders — causing stale controlsHeight or missed re-layouts.

Consider extracting a shared helper (e.g. getOpenStickyTodos) in this file and calling it from both getStickyTodosLayoutKey and the component.

Also: getStickyTodosLayoutKey lacks a test for the all-completed input case (where orderedOpenTodos is empty). Since this function is exported, a quick assertion against JSON.stringify({ ..., todos: [] }) would guard against future regressions.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented May 31, 2026

Copy link
Copy Markdown
Collaborator

Verification Report

Branch: fix/hide-completed-sticky-todos
Base: main
Environment: macOS Darwin 25.4.0, Node.js v22.17.0

Test Results

Check Command Result
StickyTodoList Tests vitest run packages/cli/src/ui/components/StickyTodoList.test.tsx ✅ 6 tests passed (230ms)
todoSnapshot Tests vitest run packages/cli/src/ui/utils/todoSnapshot.test.ts ✅ 15 tests passed (4ms)
ESLint eslint StickyTodoList.tsx StickyTodoList.test.tsx --max-warnings 0 ✅ No errors
Prettier prettier --check on changed files ✅ All files formatted
Core Build npm run build --workspace=packages/core ✅ Success
Whitespace git diff --check ✅ Clean
CLI Type Check npm run typecheck --workspace=packages/cli ⚠️ 41 errors — all pre-existing on main (30 errors), none in PR-touched files

Code Review Notes

  • Completed todos are correctly filtered out via .filter(todo => todo.status !== 'completed') in both StickyTodoList.tsx and todoSnapshot.ts
  • Original task numbers are preserved via the todoNumberById map built from the full todos array before filtering — so task "3." stays "3." even if tasks 1 and 2 are completed
  • The sticky panel returns null when orderedOpenTodos.length === 0, correctly hiding the panel when all tasks are done (new test covers this)
  • getStickyTodosLayoutKey now includes todo.status in the serialized key and filters to open todos, ensuring the layout recomputes correctly when a task completes
  • The test for layout key stability was correctly inverted from .toBe() to .not.toBe() — status changes should now trigger a layout update since the visible set changes

Verdict

✅ Ready to merge — all PR-specific checks pass, behavior changes are well-tested with updated and new test cases.


Verified by wenshao

@BZ-D BZ-D left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM. 完成的 todo 隐藏后编号保留原始顺序、layout key 与渲染逻辑保持同步,全部完成时 panel 正确隐藏。

@wenshao wenshao merged commit 093545b into QwenLM:main May 31, 2026
13 checks passed
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.

tasks가 완료된 후에도 사라지지가 않음.

3 participants