Files
oh-my-pi/packages/coding-agent/src/modes/components/todo-display.ts
T
can1357 f66e5dba9b build(config): refactored build and TypeScript configuration with Bun loaders
- Removed WASM generation script; use Bun `wasm?raw` loader for imports.
- Added bunfig.toml with loaders for `.md`, `.py`, and `.wasm?raw` text imports.
- Added types/assets/index.d.ts for global TypeScript module declarations.
- Unified TypeScript configuration with tsgo-based checking across monorepo.
- Removed build and WASM steps from install and publish pipelines.
2026-01-24 00:03:52 +01:00

105 lines
2.7 KiB
TypeScript

import * as path from "node:path";
import { Text } from "@oh-my-pi/pi-tui";
import { logger } from "@oh-my-pi/pi-utils";
import { theme } from "../../modes/theme/theme";
import type { TodoItem } from "../../modes/types";
const TODO_FILE_NAME = "todos.json";
interface TodoFile {
updatedAt: number;
todos: TodoItem[];
}
async function loadTodoFile(filePath: string): Promise<TodoFile | null> {
const file = Bun.file(filePath);
if (!(await file.exists())) return null;
try {
const text = await file.text();
const data = JSON.parse(text) as TodoFile;
if (!data || !Array.isArray(data.todos)) return null;
return data;
} catch (error) {
logger.warn("Failed to read todo file", { path: filePath, error: String(error) });
return null;
}
}
export class TodoDisplayComponent {
public todos: TodoItem[] = [];
private expanded = false;
private visible = false;
constructor(private readonly sessionFile: string | null) {}
async loadTodos(): Promise<void> {
if (!this.sessionFile) {
this.todos = [];
this.visible = false;
return;
}
const artifactsDir = this.sessionFile.slice(0, -6); // strip .jsonl extension
const todoPath = path.join(artifactsDir, TODO_FILE_NAME);
const data = await loadTodoFile(todoPath);
this.todos = data?.todos ?? [];
this.visible = this.todos.length > 0;
}
setTodos(todos: TodoItem[]): void {
this.todos = todos;
this.visible = this.todos.length > 0;
}
setExpanded(expanded: boolean): void {
this.expanded = expanded;
}
isVisible(): boolean {
return this.visible;
}
render(_width: number): string[] {
if (!this.visible || this.todos.length === 0) {
return [];
}
const lines: string[] = [];
const maxItems = this.expanded ? this.todos.length : Math.min(5, this.todos.length);
const hasMore = !this.expanded && this.todos.length > 5;
for (let i = 0; i < maxItems; i++) {
const todo = this.todos[i];
const prefix = i === 0 ? ` ${theme.tree.hook} ` : " ";
let checkbox: string;
let text: string;
if (todo.status === "completed") {
checkbox = theme.checkbox.checked;
text = theme.fg("success", `${prefix}${checkbox} ${theme.strikethrough(todo.content)}`);
} else if (todo.status === "in_progress") {
checkbox = theme.checkbox.unchecked;
text = theme.fg("accent", `${prefix}${checkbox} ${todo.content}`);
} else {
checkbox = theme.checkbox.unchecked;
text = theme.fg("dim", `${prefix}${checkbox} ${todo.content}`);
}
lines.push(text);
}
if (hasMore) {
lines.push(theme.fg("dim", ` ${theme.tree.hook} +${this.todos.length - 5} more (Ctrl+T to expand)`));
}
return lines;
}
getRenderedComponent(): Text | null {
if (!this.visible) return null;
const lines = this.render(80);
return new Text(lines.join("\n"), 0, 0);
}
}