pi-todo 1.0.0 โ 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/package.json +1 -1
- package/src/render.ts +75 -3
package/README.md
CHANGED
|
@@ -96,18 +96,30 @@ Tasks are stored in `.pi/todo.json` in the project root:
|
|
|
96
96
|
|
|
97
97
|
## Widget
|
|
98
98
|
|
|
99
|
-
The live widget appears above the editor and shows:
|
|
99
|
+
The live widget appears above the editor with a green border and shows:
|
|
100
100
|
|
|
101
101
|
```
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
103
|
+
โ ๐ TODO ยท 3 tasks โ
|
|
104
|
+
โ 2 pending ยท 1 active โ
|
|
105
|
+
โ โช T-001 [HIGH] Build StorefrontController ยท Ultron โ
|
|
106
|
+
โ โช T-002 [MED] Port landing.html ยท Maya โ
|
|
107
|
+
โ ๐ต T-003 [LOW] Verify storefront routes ยท Quinn โ
|
|
108
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
107
109
|
```
|
|
108
110
|
|
|
109
111
|
Status icons: โช pending, ๐ต in-progress, โ
done, ๐ด blocked
|
|
110
112
|
|
|
113
|
+
## Changelog
|
|
114
|
+
|
|
115
|
+
### v1.1.0
|
|
116
|
+
|
|
117
|
+
- **Bordered widget** โ The live widget now renders inside a green Unicode box border (uses `theme.fg("success")`) for better visual separation from the chat area.
|
|
118
|
+
|
|
119
|
+
### v1.0.0
|
|
120
|
+
|
|
121
|
+
- Initial release: todo tool, live widget, slash commands, per-project persistence.
|
|
122
|
+
|
|
111
123
|
## Design Decisions
|
|
112
124
|
|
|
113
125
|
- **Per-project, not global** โ Each project has its own todo list
|
package/package.json
CHANGED
package/src/render.ts
CHANGED
|
@@ -8,6 +8,78 @@ import type { TodoItem, TodoStatus, TodoPriority } from "./store.ts";
|
|
|
8
8
|
|
|
9
9
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* BorderedBox โ a container that draws a Unicode box border around its children.
|
|
13
|
+
* The border color is controlled by the `colorFn` argument (e.g. theme.fg("success", ...)).
|
|
14
|
+
*/
|
|
15
|
+
class BorderedBox implements Component {
|
|
16
|
+
private children: Component[] = [];
|
|
17
|
+
private colorFn: (text: string) => string;
|
|
18
|
+
private cachedLines: string[] | null = null;
|
|
19
|
+
private cachedWidth: number | null = null;
|
|
20
|
+
|
|
21
|
+
constructor(colorFn: (text: string) => string) {
|
|
22
|
+
this.colorFn = colorFn;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
addChild(component: Component): void {
|
|
26
|
+
this.children.push(component);
|
|
27
|
+
this.invalidate();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
invalidate(): void {
|
|
31
|
+
this.cachedLines = null;
|
|
32
|
+
this.cachedWidth = null;
|
|
33
|
+
for (const child of this.children) {
|
|
34
|
+
child.invalidate?.();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
render(width: number): string[] {
|
|
39
|
+
// Cache hit
|
|
40
|
+
if (this.cachedLines && this.cachedWidth === width) {
|
|
41
|
+
return this.cachedLines;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Inner width: total - 2 (left/right border chars)
|
|
45
|
+
const innerWidth = Math.max(0, width - 2);
|
|
46
|
+
|
|
47
|
+
// Render all children into flat line array
|
|
48
|
+
const innerLines: string[] = [];
|
|
49
|
+
for (const child of this.children) {
|
|
50
|
+
const rendered = child.render(innerWidth);
|
|
51
|
+
for (const line of rendered) {
|
|
52
|
+
innerLines.push(line);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const c = this.colorFn;
|
|
57
|
+
const topLeft = c("โ");
|
|
58
|
+
const topRight = c("โ");
|
|
59
|
+
const bottomLeft = c("โ");
|
|
60
|
+
const bottomRight = c("โ");
|
|
61
|
+
const horizontal = c("โ");
|
|
62
|
+
const vertical = c("โ");
|
|
63
|
+
|
|
64
|
+
const lines: string[] = [];
|
|
65
|
+
|
|
66
|
+
// Top border
|
|
67
|
+
lines.push(`${topLeft}${horizontal.repeat(innerWidth)}${topRight}`);
|
|
68
|
+
|
|
69
|
+
// Middle rows
|
|
70
|
+
for (const inner of innerLines) {
|
|
71
|
+
lines.push(`${vertical}${inner}${vertical}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Bottom border
|
|
75
|
+
lines.push(`${bottomLeft}${horizontal.repeat(innerWidth)}${bottomRight}`);
|
|
76
|
+
|
|
77
|
+
this.cachedLines = lines;
|
|
78
|
+
this.cachedWidth = width;
|
|
79
|
+
return lines;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
11
83
|
const STATUS_ICON: Record<TodoStatus, string> = {
|
|
12
84
|
pending: "โช",
|
|
13
85
|
"in-progress": "๐ต",
|
|
@@ -83,11 +155,11 @@ export function createWidgetComponent(items: TodoItem[]): (tui: unknown, theme:
|
|
|
83
155
|
return (_tui: unknown, theme: Theme) => {
|
|
84
156
|
const width = process.stdout.columns || 120;
|
|
85
157
|
const lines = buildWidgetLines(items, theme, width);
|
|
86
|
-
const
|
|
158
|
+
const box = new BorderedBox((text: string) => theme.fg("success", text));
|
|
87
159
|
for (const line of lines) {
|
|
88
|
-
|
|
160
|
+
box.addChild(new Text(line, 1, 0));
|
|
89
161
|
}
|
|
90
|
-
return
|
|
162
|
+
return box;
|
|
91
163
|
};
|
|
92
164
|
}
|
|
93
165
|
|