@ilikexiaoni/pi-task-list 0.4.1 → 0.4.2

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 CHANGED
@@ -13,6 +13,7 @@
13
13
  - `task-context-bridge` 通过 Pi 标准 `context` 事件注入短只读摘要,让上下文压缩后仍能看到当前、阻塞和下一步任务;它不修改 Magic Context,也不维护第二份状态。摘要按状态和总长度设上限,不注入完整证据。
14
14
  - TUI 中显示固定 5 行的紧凑任务 Widget、Footer 状态,以及 `/tasks` 可展开详细面板。
15
15
  - 紧凑 Widget 只挂载一次;任务变化时原地更新,内容未变化时不请求重绘。所有行按终端宽度截断,任务再多也不会扩高或换行。
16
+ - `/tasks` 详情使用有界居中面板,窗口大小变化时自动重算视口;fullscreen 模式接收 Pi 归一化鼠标滚轮事件,regular 模式使用跨终端键盘导航,避免依赖 Linux/Windows 各自的原始鼠标序列。
16
17
  - 工具执行模式为 sequential,避免多个并行调用互相覆盖任务状态。
17
18
 
18
19
  ## 使用
@@ -45,6 +46,24 @@
45
46
 
46
47
  当存在阻塞时,第三行优先显示最高优先级的阻塞任务及原因;没有阻塞时才显示下一项可执行任务。完整任务、依赖、备注和全部证据仍通过 `/tasks` 查看。
47
48
 
49
+ 详情面板导航:
50
+
51
+ ```text
52
+ ↑↓ / j / k 上下滚动
53
+ u / d / Space 上下翻页
54
+ Home / End / g/G 跳到开头或结尾
55
+ Esc 关闭面板
56
+ 鼠标滚轮 仅 fullscreen TUI 可用
57
+ ```
58
+
59
+ 需要鼠标滚轮时启动 fullscreen TUI:
60
+
61
+ ```powershell
62
+ pi --tui-mode fullscreen
63
+ ```
64
+
65
+ regular TUI 中终端会保留鼠标滚轮用于自己的 scrollback,这是 Pi 宿主的设计边界;插件不会拦截或伪造原始平台鼠标序列。
66
+
48
67
  典型更新:
49
68
 
50
69
  ```json
@@ -72,13 +91,13 @@ pi install npm:@ilikexiaoni/pi-task-list
72
91
  固定版本安装:
73
92
 
74
93
  ```powershell
75
- pi install npm:@ilikexiaoni/pi-task-list@0.4.1
94
+ pi install npm:@ilikexiaoni/pi-task-list@0.4.2
76
95
  ```
77
96
 
78
97
  项目级安装:
79
98
 
80
99
  ```powershell
81
- pi install -l npm:@ilikexiaoni/pi-task-list@0.4.1
100
+ pi install -l npm:@ilikexiaoni/pi-task-list@0.4.2
82
101
  ```
83
102
 
84
103
  本地开发安装:
@@ -91,7 +110,7 @@ pi install "path/to/pi-task-list"
91
110
 
92
111
  ## 发布
93
112
 
94
- 包名为 `@ilikexiaoni/pi-task-list`,使用 `pi-package` keyword,可被 Pi Package Gallery 发现。发布前先执行 `npm pack --dry-run` 检查 tarball 内容,再由已登录的 npm 账号执行 `npm publish --access public`。
113
+ 包名为 `@ilikexiaoni/pi-task-list`,使用 `pi-package` keyword,可被 Pi Package Gallery 发现。当前发布版本为 `0.4.2`。发布前先执行 `npm pack --dry-run` 检查 tarball 内容,再由已登录的 npm 账号执行 `npm publish --access public`。
95
114
 
96
115
  本包不包含测试目录、会话数据或本机配置;发布动作不会由开发脚本自动执行。
97
116
 
@@ -1,6 +1,6 @@
1
1
  import { StringEnum } from "@earendil-works/pi-ai";
2
2
  import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
3
- import { matchesKey, Text, truncateToWidth, type TUI } from "@earendil-works/pi-tui";
3
+ import { matchesKey, Text, truncateToWidth, type TUI, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
4
4
  import { Type } from "typebox";
5
5
  import {
6
6
  MAX_FIELD_LENGTH,
@@ -319,10 +319,16 @@ class TaskListComponent {
319
319
  private readonly dependencyState: TaskState = state,
320
320
  ) {}
321
321
 
322
- private pageSize(): number {
322
+ private viewportRows(): number {
323
323
  const rows = this.tui.terminal?.rows;
324
+ if (typeof rows !== "number" || rows <= 0) return 24;
325
+ // Keep the detail view inside a stable modal viewport on both TUI modes.
326
+ return Math.max(6, Math.min(rows - 2, Math.floor(rows * 0.86)));
327
+ }
328
+
329
+ private pageSize(): number {
324
330
  // Reserve one line for the footer while remaining usable on tiny terminals.
325
- return typeof rows === "number" && rows > 0 ? Math.max(1, rows - 2) : 22;
331
+ return Math.max(1, this.viewportRows() - 1);
326
332
  }
327
333
 
328
334
  private buildContent(width: number): string[] {
@@ -410,12 +416,12 @@ class TaskListComponent {
410
416
  const page = Math.max(1, this.pageSize() - 1);
411
417
  const maximum = this.maxScroll(width);
412
418
  let next = this.scrollTop;
413
- if (matchesKey(data, "up")) next -= 1;
414
- else if (matchesKey(data, "down")) next += 1;
415
- else if (matchesKey(data, "pageUp")) next -= page;
416
- else if (matchesKey(data, "pageDown")) next += page;
417
- else if (matchesKey(data, "home")) next = 0;
418
- else if (matchesKey(data, "end")) next = maximum;
419
+ if (matchesKey(data, "up") || data === "k") next -= 1;
420
+ else if (matchesKey(data, "down") || data === "j") next += 1;
421
+ else if (matchesKey(data, "pageUp") || data === "u") next -= page;
422
+ else if (matchesKey(data, "pageDown") || data === "d" || data === " ") next += page;
423
+ else if (matchesKey(data, "home") || data === "g") next = 0;
424
+ else if (matchesKey(data, "end") || data === "G") next = maximum;
419
425
  else return;
420
426
 
421
427
  next = Math.max(0, Math.min(maximum, next));
@@ -425,6 +431,21 @@ class TaskListComponent {
425
431
  this.tui.requestRender();
426
432
  }
427
433
 
434
+ handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
435
+ // Pi's regular TUI intentionally leaves the wheel to the terminal scrollback.
436
+ // Only consume normalized wheel events in fullscreen, where the host enables mouse reporting.
437
+ if (this.tui.mode !== "fullscreen" || event.type !== "wheel" || !event.wheelDelta) return undefined;
438
+
439
+ const width = Math.max(1, event.width || this.cachedWidth || this.tui.terminal?.columns || 80);
440
+ const maximum = this.maxScroll(width);
441
+ const next = Math.max(0, Math.min(maximum, this.scrollTop + event.wheelDelta));
442
+ if (next === this.scrollTop) return { handled: true, focus: true, render: false };
443
+
444
+ this.scrollTop = next;
445
+ this.invalidate();
446
+ return { handled: true, focus: true, render: true };
447
+ }
448
+
428
449
  render(width: number): string[] {
429
450
  const safeWidth = Math.max(1, width);
430
451
  const content = this.buildContent(safeWidth);
@@ -437,7 +458,10 @@ class TaskListComponent {
437
458
  const visible = content.slice(this.scrollTop, this.scrollTop + viewport);
438
459
  while (visible.length < viewport) visible.push("");
439
460
  const position = content.length === 0 ? "0/0" : `${this.scrollTop + 1}-${Math.min(content.length, this.scrollTop + viewport)}/${content.length}`;
440
- visible.push(truncateToWidth(` ${this.theme.fg("dim", `位置 ${position} · ↑↓ 滚动 · PageUp/PageDown 翻页 · Home/End 跳转 · Esc 关闭`)}`, safeWidth));
461
+ const navigation = this.tui.mode === "fullscreen"
462
+ ? "滚轮/↑↓/jk 滚动 · u/d/Space 翻页 · Home/End/g/G 跳转 · Esc 关闭"
463
+ : "↑↓/jk 滚动 · u/d/Space 翻页 · Home/End/g/G 跳转 · Esc 关闭(fullscreen 支持滚轮)";
464
+ visible.push(truncateToWidth(` ${this.theme.fg("dim", `位置 ${position} · ${navigation}`)}`, safeWidth));
441
465
 
442
466
  this.cachedWidth = safeWidth;
443
467
  this.cachedOffset = this.scrollTop;
@@ -642,7 +666,22 @@ export default function taskListExtension(pi: ExtensionAPI) {
642
666
  );
643
667
  return;
644
668
  }
645
- await ctx.ui.custom<void>((tui, theme, _keybindings, done) => new TaskListComponent(selected.state, theme, selected.title, tui, () => done(), selected.dependencyState));
669
+ await ctx.ui.custom<void>(
670
+ (tui, theme, _keybindings, done) => new TaskListComponent(selected.state, theme, selected.title, tui, () => done(), selected.dependencyState),
671
+ {
672
+ // A modal gives fullscreen mode a bounded hit target for normalized wheel events.
673
+ // Regular mode still uses keyboard navigation because Pi delegates its wheel to scrollback.
674
+ overlay: true,
675
+ overlayOptions: () => {
676
+ return {
677
+ width: "92%",
678
+ maxHeight: "86%",
679
+ anchor: "center",
680
+ margin: 1,
681
+ };
682
+ },
683
+ },
684
+ );
646
685
  },
647
686
  });
648
687
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilikexiaoni/pi-task-list",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "description": "A generic structured task list extension for Pi with dependencies, evidence, and visible progress.",
6
6
  "keywords": [