pi-jar 0.3.0 → 0.3.1
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/extensions/index.ts +6 -3
- package/package.json +1 -1
- package/src/composer.ts +16 -10
- package/src/task-widget.ts +23 -3
package/extensions/index.ts
CHANGED
|
@@ -168,10 +168,13 @@ export default function piJar(pi: ExtensionAPI): void {
|
|
|
168
168
|
if (open.length) todosAcknowledged = false;
|
|
169
169
|
const visible = enabled && items.length > 0 && (open.length > 0 || !todosAcknowledged);
|
|
170
170
|
try {
|
|
171
|
-
// Rows are rebuilt only when the list changes (every change reinstalls the widget), the width or the theme.
|
|
172
171
|
ctx.ui.setWidget("pi-jar.todos", !visible ? undefined : (tui, theme) =>
|
|
173
|
-
taskWidget(tui, theme, () => todos?.all() ?? [], taskWidgetState
|
|
174
|
-
|
|
172
|
+
taskWidget(tui, theme, () => todos?.all() ?? [], taskWidgetState, {
|
|
173
|
+
render: (width) => composer.enabled ? composer.renderMascot(width) : undefined,
|
|
174
|
+
poke: () => composer.pokeMascot()
|
|
175
|
+
}));
|
|
176
|
+
composer.setTasksVisible(visible);
|
|
177
|
+
} catch { composer.setTasksVisible(false); /* Optional widget; task data remains available via /jar tasks and jar_todo. */ }
|
|
175
178
|
};
|
|
176
179
|
registerTaskTool(pi, () => todos, (ctx) => updateTaskWidget(ctx));
|
|
177
180
|
let changes: ChangeTracker | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-jar",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A warm, role-aware Pi terminal workspace with switchable profiles, animated UI, plan and goal workflows, tasks, change review, model roles, background shells and subagents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/composer.ts
CHANGED
|
@@ -318,6 +318,7 @@ export class ComposerStyle {
|
|
|
318
318
|
private phase: WorkingPhase = "idle";
|
|
319
319
|
private animations = true;
|
|
320
320
|
private mascotOn = true;
|
|
321
|
+
private tasksVisible = false;
|
|
321
322
|
private session = "";
|
|
322
323
|
private profile = "Default";
|
|
323
324
|
private cwd = process.cwd();
|
|
@@ -338,16 +339,7 @@ export class ComposerStyle {
|
|
|
338
339
|
const mood = this.animations ? this.mascot.mood() : "idle";
|
|
339
340
|
return paintFace(mascotFace(mood), mood, this.colors ? (color, text) => this.colors!.fg(color, text) : undefined);
|
|
340
341
|
},
|
|
341
|
-
perch: (width) =>
|
|
342
|
-
if (!this.mascotOn || width < 40) return undefined;
|
|
343
|
-
const mood = this.animations ? this.mascot.mood() : "idle";
|
|
344
|
-
const frame = this.animations ? Math.floor(Date.now() / COMPOSER_ACTIVE_INTERVAL_MS) : 0;
|
|
345
|
-
if (this.fullSprite(width)) {
|
|
346
|
-
return mascotPerch(mood, frame, this.colors ? (color, text) => this.colors!.fg(color, text) : undefined)
|
|
347
|
-
.map(row => " " + row);
|
|
348
|
-
}
|
|
349
|
-
return [" " + paintTip(mascotTip(mood, frame), mood, this.colors ? (color, text) => this.colors!.fg(color, text) : undefined)];
|
|
350
|
-
},
|
|
342
|
+
perch: (width) => this.tasksVisible ? undefined : this.renderMascot(width),
|
|
351
343
|
session: () => this.session,
|
|
352
344
|
profile: () => this.profile,
|
|
353
345
|
ghost: () => this.suggestions?.text,
|
|
@@ -374,6 +366,20 @@ export class ComposerStyle {
|
|
|
374
366
|
}
|
|
375
367
|
};
|
|
376
368
|
|
|
369
|
+
/** Tasks share the mascot's perch instead of reserving a separate vertical widget. */
|
|
370
|
+
setTasksVisible(visible: boolean): void { this.tasksVisible = visible; }
|
|
371
|
+
pokeMascot(): void { this.decor.poke(); }
|
|
372
|
+
renderMascot(width: number): string[] | undefined {
|
|
373
|
+
if (!this.mascotOn || width < 40) return undefined;
|
|
374
|
+
const mood = this.animations ? this.mascot.mood() : "idle";
|
|
375
|
+
const frame = this.animations ? Math.floor(Date.now() / COMPOSER_ACTIVE_INTERVAL_MS) : 0;
|
|
376
|
+
if (this.fullSprite(width)) {
|
|
377
|
+
return mascotPerch(mood, frame, this.colors ? (color, text) => this.colors!.fg(color, text) : undefined)
|
|
378
|
+
.map(row => " " + row);
|
|
379
|
+
}
|
|
380
|
+
return [" " + paintTip(mascotTip(mood, frame), mood, this.colors ? (color, text) => this.colors!.fg(color, text) : undefined)];
|
|
381
|
+
}
|
|
382
|
+
|
|
377
383
|
/** Keep short/narrow terminals usable; the pet and mood spark reserve six rows. */
|
|
378
384
|
private fullSprite(width: number): boolean {
|
|
379
385
|
return width >= 60 && (this.tui?.terminal?.rows ?? process.stdout.rows ?? 24) >= 24;
|
package/src/task-widget.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { Key, matchesKey, truncateToWidth, type TUI, type TuiMouseEvent } from "@earendil-works/pi-tui";
|
|
2
|
+
import { Key, matchesKey, truncateToWidth, visibleWidth, type TUI, type TuiMouseEvent } from "@earendil-works/pi-tui";
|
|
3
3
|
import { todoProgress, todoTotals, type Todo } from "./tasks.ts";
|
|
4
4
|
import { todoRow } from "./task-tool.ts";
|
|
5
5
|
|
|
6
6
|
/** Retained across widget refreshes. Compact by default so tasks never bury the composer. */
|
|
7
7
|
export interface TaskWidgetState { expanded: boolean; scroll: number | undefined }
|
|
8
|
-
export function taskWidget(tui: TUI, theme: Theme, items: () => Todo[], state: TaskWidgetState
|
|
8
|
+
export function taskWidget(tui: TUI, theme: Theme, items: () => Todo[], state: TaskWidgetState,
|
|
9
|
+
mascot?: { render(width: number): string[] | undefined; poke(): void }) {
|
|
9
10
|
let page = 4;
|
|
11
|
+
let taskX = 0;
|
|
12
|
+
let taskY = 0;
|
|
13
|
+
let mascotRows: string[] = [];
|
|
10
14
|
const toggle = () => { state.expanded = !state.expanded; tui.requestRender(); };
|
|
11
15
|
const move = (delta: number) => {
|
|
12
16
|
state.scroll = Math.max(0, Math.min(Math.max(0, items().length - page), (state.scroll ?? 0) + delta));
|
|
@@ -22,10 +26,21 @@ export function taskWidget(tui: TUI, theme: Theme, items: () => Todo[], state: T
|
|
|
22
26
|
else if (matchesKey(data, Key.pageUp)) move(-page);
|
|
23
27
|
},
|
|
24
28
|
handleMouse(event: TuiMouseEvent) {
|
|
29
|
+
if (event.y < mascotRows.length && event.x >= 3 && event.x < visibleWidth(mascotRows[event.y] ?? "")) {
|
|
30
|
+
if (event.type === "click" && event.button === "left") { mascot?.poke(); return { handled: true }; }
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (event.x < taskX || event.y < taskY) return;
|
|
34
|
+
event = { ...event, x: event.x - taskX, y: event.y - taskY };
|
|
25
35
|
if (event.type === "click" && event.button === "left" && event.y === 0) { toggle(); return { handled: true }; }
|
|
26
36
|
if (event.type === "wheel" && event.wheelDelta && state.expanded) { move(Math.sign(event.wheelDelta) * 3); return { handled: true }; }
|
|
27
37
|
},
|
|
28
38
|
render(width: number) {
|
|
39
|
+
mascotRows = mascot?.render(width) ?? [];
|
|
40
|
+
const mascotWidth = Math.max(0, ...mascotRows.map(visibleWidth));
|
|
41
|
+
taskX = mascotRows.length && width >= 60 && width - mascotWidth - 2 >= 24 ? mascotWidth + 2 : 0;
|
|
42
|
+
taskY = taskX ? 0 : mascotRows.length;
|
|
43
|
+
const taskWidth = Math.max(0, width - taskX);
|
|
29
44
|
const all = items();
|
|
30
45
|
const { done, total } = todoTotals(all);
|
|
31
46
|
const parents = new Set(all.flatMap((item) => item.parentId ? [item.parentId] : []));
|
|
@@ -45,7 +60,12 @@ export function taskWidget(tui: TUI, theme: Theme, items: () => Todo[], state: T
|
|
|
45
60
|
rows.push(...all.slice(start, start + page).map((item) => todoRow(item, (color, text) => theme.fg(color, text), (text) => theme.bold(text), parents.has(item.id) ? todoProgress(all, item.id) : undefined)));
|
|
46
61
|
}
|
|
47
62
|
rows.push(" ");
|
|
48
|
-
|
|
63
|
+
const tasks = rows.slice(0, Math.max(1, budget)).map((line) => truncateToWidth(line, taskWidth));
|
|
64
|
+
if (!taskX) return [...mascotRows, ...tasks];
|
|
65
|
+
return Array.from({ length: Math.max(mascotRows.length, tasks.length) }, (_, index) => {
|
|
66
|
+
const pet = mascotRows[index] ?? "";
|
|
67
|
+
return pet + " ".repeat(taskX - visibleWidth(pet)) + (tasks[index] ?? "");
|
|
68
|
+
});
|
|
49
69
|
}
|
|
50
70
|
};
|
|
51
71
|
}
|