feat(tracker): polish UI sorting and formatting (#22437)

This commit is contained in:
anj-s
2026-03-16 12:18:01 -07:00
committed by GitHub
parent 05fda0cf01
commit bba9c07541
3 changed files with 58 additions and 10 deletions

View File

@@ -22,7 +22,6 @@ export const TASK_TYPE_LABELS: Record<TaskType, string> = {
export enum TaskStatus {
OPEN = 'open',
IN_PROGRESS = 'in_progress',
BLOCKED = 'blocked',
CLOSED = 'closed',
}
export const TaskStatusSchema = z.nativeEnum(TaskStatus);

View File

@@ -186,20 +186,55 @@ describe('Tracker Tools Integration', () => {
expect(display.todos).toEqual([
{
description: `[p1] [TASK] Parent`,
description: `task: Parent (p1)`,
status: 'in_progress',
},
{
description: ` [c1] [EPIC] Child`,
description: ` epic: Child (c1)`,
status: 'pending',
},
{
description: ` [leaf] [BUG] Closed Leaf`,
description: ` bug: Closed Leaf (leaf)`,
status: 'completed',
},
]);
});
it('sorts tasks by status', async () => {
const t1 = {
id: 't1',
title: 'T1',
type: TaskType.TASK,
status: TaskStatus.CLOSED,
dependencies: [],
};
const t2 = {
id: 't2',
title: 'T2',
type: TaskType.TASK,
status: TaskStatus.OPEN,
dependencies: [],
};
const t3 = {
id: 't3',
title: 'T3',
type: TaskType.TASK,
status: TaskStatus.IN_PROGRESS,
dependencies: [],
};
const mockService = {
listTasks: async () => [t1, t2, t3],
} as unknown as TrackerService;
const display = await buildTodosReturnDisplay(mockService);
expect(display.todos).toEqual([
{ description: `task: T3 (t3)`, status: 'in_progress' },
{ description: `task: T2 (t2)`, status: 'pending' },
{ description: `task: T1 (t1)`, status: 'completed' },
]);
});
it('detects cycles', async () => {
// Since TrackerTask only has a single parentId, a true cycle is unreachable from roots.
// We simulate a database corruption (two tasks with same ID, one root, one child)
@@ -220,7 +255,7 @@ describe('Tracker Tools Integration', () => {
expect(display.todos).toEqual([
{
description: `[p1] [TASK] Parent`,
description: `task: Parent (p1)`,
status: 'pending',
},
{

View File

@@ -23,7 +23,7 @@ import {
TRACKER_UPDATE_TASK_TOOL_NAME,
TRACKER_VISUALIZE_TOOL_NAME,
} from './tool-names.js';
import type { ToolResult, TodoList } from './tools.js';
import type { ToolResult, TodoList, TodoStatus } from './tools.js';
import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js';
import { ToolErrorType } from './tool-error.js';
import type { TrackerTask, TaskType } from '../services/trackerTypes.js';
@@ -48,6 +48,21 @@ export async function buildTodosReturnDisplay(
}
}
const statusOrder = {
[TaskStatus.IN_PROGRESS]: 0,
[TaskStatus.OPEN]: 1,
[TaskStatus.CLOSED]: 2,
};
const sortTasks = (a: TrackerTask, b: TrackerTask) => {
if (statusOrder[a.status] !== statusOrder[b.status]) {
return statusOrder[a.status] - statusOrder[b.status];
}
return a.id.localeCompare(b.id);
};
roots.sort(sortTasks);
const todos: TodoList['todos'] = [];
const addTask = (task: TrackerTask, depth: number, visited: Set<string>) => {
@@ -60,8 +75,7 @@ export async function buildTodosReturnDisplay(
}
visited.add(task.id);
let status: 'pending' | 'in_progress' | 'completed' | 'cancelled' =
'pending';
let status: TodoStatus = 'pending';
if (task.status === TaskStatus.IN_PROGRESS) {
status = 'in_progress';
} else if (task.status === TaskStatus.CLOSED) {
@@ -69,11 +83,12 @@ export async function buildTodosReturnDisplay(
}
const indent = ' '.repeat(depth);
const description = `${indent}[${task.id}] ${TASK_TYPE_LABELS[task.type]} ${task.title}`;
const description = `${indent}${task.type}: ${task.title} (${task.id})`;
todos.push({ description, status });
const children = childrenMap.get(task.id) ?? [];
children.sort(sortTasks);
for (const child of children) {
addTask(child, depth + 1, visited);
}
@@ -570,7 +585,6 @@ class TrackerVisualizeInvocation extends BaseToolInvocation<
const statusEmojis: Record<TaskStatus, string> = {
open: '⭕',
in_progress: '🚧',
blocked: '🚫',
closed: '✅',
};