Rename task widget to todos
This commit is contained in:
@@ -98,10 +98,10 @@ A task starts timing the first time it enters `in-progress`. Leaving that state
|
||||
|
||||
## Pi Widget
|
||||
|
||||
The widget appears above Pi's editor while tasks exist. Run `/tasks` to hide or show it for the current Pi runtime. Its header is the virtual task-tree root. Root tasks render as Roman-numeral categories, their children as numbered items, and leaf sub-items use indentation without another label. Internal task IDs remain unchanged and visible in tool output. In-progress circles rotate through `◐ ◓ ◑ ◒` on the existing one-second timer refresh; other status symbols remain still. The widget shows at most 12 task rows plus a compact count of hidden tasks, so larger plans do not crowd the editor out of an 80×24 terminal.
|
||||
The widget appears above Pi's editor while todos exist. Run `/todo` to hide or show it for the current Pi runtime. Its header is the virtual task-tree root. Root tasks render as Roman-numeral categories, their children as numbered items, and leaf sub-items use indentation without another label. Internal task IDs remain unchanged and visible in tool output. In-progress circles rotate through `◐ ◓ ◑ ◒` twice per second; other status symbols remain still. The widget shows at most 12 task rows plus a compact count of hidden tasks, so larger plans do not crowd the editor out of an 80×24 terminal.
|
||||
|
||||
```text
|
||||
Tasks 2/6 · !1 ━━━━━────────
|
||||
Todos 2/6 · !1 ━━━━━────────
|
||||
I. ◐ Build auth · 12:08
|
||||
├─ 1. ! Resolve token issue · 03:41
|
||||
│ └─ ○ Inspect provider response
|
||||
|
||||
@@ -42,22 +42,22 @@ export default function piTodoHerdr(pi: ExtensionAPI): void {
|
||||
|
||||
registerTaskTools(pi, state, refresh);
|
||||
|
||||
pi.registerCommand("tasks", {
|
||||
description: "Show or hide the task tree widget",
|
||||
pi.registerCommand("todo", {
|
||||
description: "Show or hide the todo tree widget",
|
||||
handler: async (_args, ctx) => {
|
||||
if (state.list().length === 0) {
|
||||
clearTaskWidget(ctx);
|
||||
ctx.ui.notify("No tasks yet.", "info");
|
||||
ctx.ui.notify("No todos yet.", "info");
|
||||
return;
|
||||
}
|
||||
|
||||
widgetVisible = !widgetVisible;
|
||||
if (widgetVisible) {
|
||||
updateTaskWidget(state, ctx, true);
|
||||
ctx.ui.notify("Task widget shown.", "info");
|
||||
ctx.ui.notify("Todo widget shown.", "info");
|
||||
} else {
|
||||
clearTaskWidget(ctx);
|
||||
ctx.ui.notify("Task widget hidden.", "info");
|
||||
ctx.ui.notify("Todo widget hidden.", "info");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -210,7 +210,7 @@ export function renderWidgetTree(
|
||||
|
||||
export function progressLabel(stats: TaskStats): string {
|
||||
const blocked = stats.blocked > 0 ? ` · !${stats.blocked}` : "";
|
||||
return ` Tasks ${stats.done}/${stats.total}${blocked}`;
|
||||
return ` Todos ${stats.done}/${stats.total}${blocked}`;
|
||||
}
|
||||
|
||||
export function renderProgressHeader(stats: TaskStats, theme: Theme, width: number): string {
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import type { TaskState } from "./state.ts";
|
||||
import { renderProgressHeader, renderWidgetTree } from "./ui.ts";
|
||||
|
||||
const WIDGET_ID = "pi-todo-herdr";
|
||||
const TIMER_REFRESH_MS = 1000;
|
||||
export const TODO_ANIMATION_INTERVAL_MS = 500;
|
||||
|
||||
export function clearTaskWidget(ctx: ExtensionContext): void {
|
||||
ctx.ui.setWidget(WIDGET_ID, undefined);
|
||||
@@ -13,7 +13,7 @@ export function updateTaskWidget(
|
||||
state: TaskState,
|
||||
ctx: ExtensionContext,
|
||||
visible: boolean,
|
||||
refreshMs = TIMER_REFRESH_MS,
|
||||
refreshMs = TODO_ANIMATION_INTERVAL_MS,
|
||||
): void {
|
||||
const tasks = state.list();
|
||||
if (!visible || tasks.length === 0) {
|
||||
|
||||
@@ -10,6 +10,38 @@ import {
|
||||
} from "../src/prompt.ts";
|
||||
import { TaskState } from "../src/state.ts";
|
||||
|
||||
test("registers the conflict-free /todo command", async () => {
|
||||
type RegisteredCommand = {
|
||||
description: string;
|
||||
handler(args: string, ctx: unknown): Promise<void>;
|
||||
};
|
||||
const commands = new Map<string, RegisteredCommand>();
|
||||
const pi = {
|
||||
on() {},
|
||||
registerTool() {},
|
||||
registerCommand(name: string, command: RegisteredCommand) {
|
||||
commands.set(name, command);
|
||||
},
|
||||
};
|
||||
|
||||
piTodoHerdr(pi as never);
|
||||
|
||||
assert.deepEqual([...commands.keys()], ["todo"]);
|
||||
const todo = commands.get("todo");
|
||||
assert.equal(todo?.description, "Show or hide the todo tree widget");
|
||||
|
||||
const notifications: string[] = [];
|
||||
await todo?.handler("", {
|
||||
ui: {
|
||||
setWidget() {},
|
||||
notify(message: string) {
|
||||
notifications.push(message);
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.deepEqual(notifications, ["No todos yet."]);
|
||||
});
|
||||
|
||||
test("keeps task guidance short with planning after exploration", () => {
|
||||
assert.equal(TASK_PROMPT_GUIDELINES.length, 2);
|
||||
const prompt = TASK_PROMPT_GUIDELINES.join("\n");
|
||||
|
||||
+6
-2
@@ -12,7 +12,7 @@ import {
|
||||
renderWidgetTree,
|
||||
snapshotStats,
|
||||
} from "../src/ui.ts";
|
||||
import { updateTaskWidget } from "../src/widget.ts";
|
||||
import { TODO_ANIMATION_INTERVAL_MS, updateTaskWidget } from "../src/widget.ts";
|
||||
|
||||
const plainTheme = {
|
||||
fg: (_color: string, text: string) => text,
|
||||
@@ -49,7 +49,7 @@ function fixture(): TaskState {
|
||||
test("renders an adaptive progress bar and stays within width", () => {
|
||||
const stats = fixture().stats();
|
||||
const wide = stripAnsi(renderProgressHeader(stats, plainTheme as never, 40));
|
||||
assert.match(wide, /^ Tasks 1\/5 · !1 ━{4}─{16}$/);
|
||||
assert.match(wide, /^ Todos 1\/5 · !1 ━{4}─{16}$/);
|
||||
assert.ok(visibleWidth(wide) <= 40);
|
||||
|
||||
const narrow = renderProgressHeader(stats, plainTheme as never, 12);
|
||||
@@ -353,6 +353,10 @@ test("hides an empty widget and renders a populated one", () => {
|
||||
assert.ok(lines.every((line) => visibleWidth(line) <= 30));
|
||||
});
|
||||
|
||||
test("animates active todos twice per second", () => {
|
||||
assert.equal(TODO_ANIMATION_INTERVAL_MS, 500);
|
||||
});
|
||||
|
||||
test("live widgets request rerenders and dispose their interval", async () => {
|
||||
let now = 0;
|
||||
const state = new TaskState(() => now);
|
||||
|
||||
Reference in New Issue
Block a user