fix: 修复任务列表数字ID乱序和内存轮询导致滚动跳动
两个修复: 1. 任务列表数字ID乱序(#32):新增 compareTaskIds 比较函数,纯数字ID按 数值大小排序(1, 2, 3, 10 而非 1, 10, 2, 3),非纯数字回退到 localeCompare 自然排序。listTasks 和 taskStateMessage 两处排序入口 均已修复。 2. 终端 RSS 内存轮询引发滚动跳动(#31):useRssDisplay hook 新增 isLoading 参数,仅在加载中启用定时轮询,闲时仅渲染一次内存占用值,避免 Ink 重渲染 干扰用户滚动查看结果。 附:TaskCreateTool.isConcurrencySafe 改为 false,防止并发任务创建的文件 IO 竞态。 Co-Authored-By: CoStrict-DeepSeek-V4-Pro <deepseek-ai@claude-code-best.win>
This commit is contained in:
parent
5c2e1918ca
commit
5ee2d6dcc3
|
|
@ -69,7 +69,7 @@ export const TaskCreateTool = buildTool({
|
|||
return isTodoV2Enabled()
|
||||
},
|
||||
isConcurrencySafe() {
|
||||
return true
|
||||
return false
|
||||
},
|
||||
toAutoClassifierInput(input) {
|
||||
return input.subject
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ const RSS_UPDATE_INTERVAL_MS = 5_000;
|
|||
|
||||
type RssState = { text: string; level: 'normal' | 'warning' | 'error' };
|
||||
|
||||
function useRssDisplay(): RssState | null {
|
||||
function useRssDisplay(isLoading: boolean): RssState | null {
|
||||
const [state, setState] = useState<RssState | null>(null);
|
||||
useEffect(() => {
|
||||
function update(): void {
|
||||
|
|
@ -68,9 +68,10 @@ function useRssDisplay(): RssState | null {
|
|||
setState(prev => (prev?.text === text ? prev : { text, level }));
|
||||
}
|
||||
update();
|
||||
if (!isLoading) return;
|
||||
const timer = setInterval(update, RSS_UPDATE_INTERVAL_MS);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
}, [isLoading]);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
|
@ -279,7 +280,7 @@ function ModeIndicator({
|
|||
}
|
||||
}, [voiceEnabled, voiceHintUnderCap]);
|
||||
const isKillAgentsConfirmShowing = useAppState(s => s.notifications.current?.key === 'kill-agents-confirm');
|
||||
const rssState = useRssDisplay();
|
||||
const rssState = useRssDisplay(isLoading);
|
||||
|
||||
// Derive team info from teamContext (no filesystem I/O needed)
|
||||
// Match the same logic as TeamStatus to avoid trailing separator
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import {
|
|||
setLeaderTeamName,
|
||||
clearLeaderTeamName,
|
||||
isTodoV2Enabled,
|
||||
compareTaskIds,
|
||||
type Task,
|
||||
} from '../tasks'
|
||||
|
||||
|
|
@ -349,6 +350,11 @@ describe('listTasks', () => {
|
|||
const subjects = tasks.map(t => t.subject).sort()
|
||||
expect(subjects).toEqual(['A', 'B'])
|
||||
})
|
||||
|
||||
test('sorts task IDs numerically', () => {
|
||||
const ids = ['3', '10', '2', '1']
|
||||
expect(ids.sort(compareTaskIds)).toEqual(['1', '2', '3', '10'])
|
||||
})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { randomUUID } from 'crypto'
|
||||
import type { SDKMessage } from 'src/entrypoints/agentSdkTypes.js'
|
||||
import type { Task } from './tasks.js'
|
||||
import { compareTaskIds } from './tasks.js'
|
||||
|
||||
export type TaskStateItem = Pick<
|
||||
Task,
|
||||
|
|
@ -37,7 +38,7 @@ function toTaskStateItem(task: Task): TaskStateItem {
|
|||
}
|
||||
|
||||
function compareTaskStateItems(a: TaskStateItem, b: TaskStateItem): number {
|
||||
return a.id.localeCompare(b.id)
|
||||
return compareTaskIds(a.id, b.id)
|
||||
}
|
||||
|
||||
export function buildTaskStateSnapshot(
|
||||
|
|
|
|||
|
|
@ -230,6 +230,18 @@ export function getTaskPath(taskListId: string, taskId: string): string {
|
|||
return join(getTasksDir(taskListId), `${sanitizePathComponent(taskId)}.json`)
|
||||
}
|
||||
|
||||
export function compareTaskIds(a: string, b: string): number {
|
||||
const aIsNumeric = /^\d+$/.test(a)
|
||||
const bIsNumeric = /^\d+$/.test(b)
|
||||
if (aIsNumeric && bIsNumeric) {
|
||||
return Number(a) - Number(b)
|
||||
}
|
||||
if (aIsNumeric !== bIsNumeric) {
|
||||
return aIsNumeric ? -1 : 1
|
||||
}
|
||||
return a.localeCompare(b, undefined, { numeric: true })
|
||||
}
|
||||
|
||||
export async function ensureTasksDir(taskListId: string): Promise<void> {
|
||||
const dir = getTasksDir(taskListId)
|
||||
try {
|
||||
|
|
@ -451,6 +463,7 @@ export async function listTasks(taskListId: string): Promise<Task[]> {
|
|||
const taskIds = files
|
||||
.filter(f => f.endsWith('.json'))
|
||||
.map(f => f.replace('.json', ''))
|
||||
.sort(compareTaskIds)
|
||||
const results = await Promise.all(taskIds.map(id => getTask(taskListId, id)))
|
||||
return results.filter((t): t is Task => t !== null)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user