@narumitw/pi-statusline 0.49.5 → 0.49.6

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
@@ -22,7 +22,7 @@ A representative uncolored layout:
22
22
  available under **Advanced**.
23
23
 
24
24
  > **Need more customization?** See
25
- > [`pi-starship`](https://github.com/narumiruna/pi-extensions/tree/main/extensions/pi-starship)
25
+ > [`pi-starship`](https://github.com/narumiruna/pi-extensions/tree/main/packages/pi-starship)
26
26
  > ([npm](https://www.npmjs.com/package/@narumitw/pi-starship)). It uses a
27
27
  > [Starship-inspired](https://starship.rs/) TOML format and style syntax for deeper control over
28
28
  > layout, modules, and colors. Choose `pi-statusline` for practical defaults and quick setup.
@@ -49,7 +49,7 @@ For the best result, use a terminal font that includes Powerline glyphs and emoj
49
49
  1. Install the extension and start Pi.
50
50
  2. Run `/statusline`.
51
51
  3. Use the standard Main/Advanced navigation, then choose an appearance or information level;
52
- specialized previews still apply changes immediately.
52
+ appearance previews update the extension-owned footer before you apply a choice.
53
53
 
54
54
  ```text
55
55
  Appearance (tokyo-night)
@@ -93,9 +93,9 @@ reads or writes.
93
93
 
94
94
  The direct `settings`, `status`, and `help` routes remain for compatibility. The root standard menu
95
95
  is TUI-only; Escape returns from Advanced or closes Main. RPC receives observable notifications
96
- instead of TUI-only controls. Unknown subcommands and trailing arguments are rejected. Palette and
97
- information previews, the width-aware layout editor, and the JSON editor remain specialized UI
98
- because they provide live preview/edit behavior rather than standard navigation.
96
+ instead of TUI-only controls. Unknown subcommands and trailing arguments are rejected. The standard
97
+ palette picker owns navigation and cleanup while pi-statusline still owns footer previews, settings,
98
+ and rollback. The width-aware layout editor and JSON editor remain specialized UI.
99
99
 
100
100
  ## 📐 Runtime behavior
101
101
 
@@ -334,7 +334,7 @@ Put transient activity in the value and always clear the same complete key.
334
334
  ## 🗂️ Package layout
335
335
 
336
336
  ```text
337
- extensions/pi-statusline/
337
+ packages/pi-statusline/
338
338
  ├── src/
339
339
  │ ├── index.ts # thin entrypoint forwarding to the source runtime
340
340
  │ ├── statusline.ts # authoritative lifecycle implementation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-statusline",
3
- "version": "0.49.5",
3
+ "version": "0.49.6",
4
4
  "description": "Pi extension that replaces the footer with an information-rich statusline.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,6 +22,9 @@
22
22
  "./src/index.ts"
23
23
  ]
24
24
  },
25
+ "piExtension": {
26
+ "lifecycle": "stable"
27
+ },
25
28
  "scripts": {
26
29
  "check": "biome check --vcs-use-ignore-file=false src test package.json tsconfig.json README.md && npm run typecheck",
27
30
  "format": "biome check --write --vcs-use-ignore-file=false src test package.json tsconfig.json README.md",
@@ -33,17 +36,17 @@
33
36
  },
34
37
  "devDependencies": {
35
38
  "@biomejs/biome": "2.5.7",
36
- "@earendil-works/pi-coding-agent": "0.83.0",
37
- "@earendil-works/pi-tui": "0.83.0",
39
+ "@earendil-works/pi-coding-agent": "0.84.1",
40
+ "@earendil-works/pi-tui": "0.84.1",
38
41
  "@types/node": "26.1.2",
39
42
  "typescript": "7.0.2"
40
43
  },
41
44
  "repository": {
42
45
  "type": "git",
43
46
  "url": "https://github.com/narumiruna/pi-extensions",
44
- "directory": "extensions/pi-statusline"
47
+ "directory": "packages/pi-statusline"
45
48
  },
46
49
  "dependencies": {
47
- "@narumitw/pi-tui-kit": "^0.49.1"
50
+ "@narumitw/pi-tui-kit": "^0.52.0"
48
51
  }
49
52
  }
package/src/commands.ts CHANGED
@@ -7,8 +7,6 @@ import {
7
7
  Container,
8
8
  Key,
9
9
  matchesKey,
10
- type SelectItem,
11
- SelectList,
12
10
  Text,
13
11
  truncateToWidth,
14
12
  wrapTextWithAnsi,
@@ -56,13 +54,18 @@ const SEGMENT_DESCRIPTIONS: Record<SegmentName, string> = {
56
54
  time: "Current local time",
57
55
  turn: "Current session turn count",
58
56
  };
57
+ interface StatuslineMenuOwner {
58
+ signal: AbortSignal;
59
+ isCurrent(): boolean;
60
+ }
61
+
59
62
  export interface StatuslineCommandOptions {
60
63
  settingsPath: string;
61
64
  getLoaded(): LoadedStatuslineSettings;
62
65
  apply(loaded: LoadedStatuslineSettings, ctx: ExtensionCommandContext): void;
63
66
  preview?(palettePreset: PalettePreset | undefined, ctx: ExtensionCommandContext): void;
64
67
  save?: (settingsPath: string, rawDocument: string) => LoadedStatuslineSettings;
65
- getMenuOwner?(): { signal: AbortSignal; isCurrent(): boolean };
68
+ getMenuOwner?(): StatuslineMenuOwner;
66
69
  }
67
70
 
68
71
  export function registerStatuslineCommand(pi: ExtensionAPI, options: StatuslineCommandOptions) {
@@ -191,7 +194,7 @@ async function showMainMenu(ctx: ExtensionCommandContext, options: StatuslineCom
191
194
  },
192
195
  actions: {
193
196
  appearance: async () => {
194
- await choosePalettePreset(ctx, options);
197
+ await choosePalettePreset(ctx, options, owner);
195
198
  return { kind: "close" };
196
199
  },
197
200
  setInformation: async ({ itemId }) => {
@@ -233,6 +236,7 @@ async function showMainMenu(ctx: ExtensionCommandContext, options: StatuslineCom
233
236
  async function choosePalettePreset(
234
237
  ctx: ExtensionCommandContext,
235
238
  options: StatuslineCommandOptions,
239
+ owner: StatuslineMenuOwner,
236
240
  ) {
237
241
  if (ctx.mode !== "tui") {
238
242
  if (ctx.hasUI) ctx.ui.notify(`Edit palettePreset manually: ${options.settingsPath}`, "info");
@@ -241,10 +245,19 @@ async function choosePalettePreset(
241
245
  const current = options.getLoaded();
242
246
  let selection: PalettePreset | undefined;
243
247
  try {
244
- selection = await showPalettePresetPicker(ctx, current.config.palettePreset, options);
248
+ selection = await showPalettePresetPicker(ctx, current.config.palettePreset, options, owner);
245
249
  } finally {
246
- options.preview?.(undefined, ctx);
250
+ if (!owner.signal.aborted && owner.isCurrent()) {
251
+ try {
252
+ options.preview?.(undefined, ctx);
253
+ } catch (error) {
254
+ if (canNotify(ctx)) {
255
+ ctx.ui.notify(`Palette preview could not be reset: ${formatError(error)}`, "error");
256
+ }
257
+ }
258
+ }
247
259
  }
260
+ if (owner.signal.aborted || !owner.isCurrent()) return;
248
261
  if (selection === undefined) return;
249
262
 
250
263
  let loaded: LoadedStatuslineSettings;
@@ -278,60 +291,27 @@ async function showPalettePresetPicker(
278
291
  ctx: ExtensionCommandContext,
279
292
  current: PalettePreset,
280
293
  options: StatuslineCommandOptions,
294
+ owner: StatuslineMenuOwner,
281
295
  ): Promise<PalettePreset | undefined> {
282
- const items: SelectItem[] = PALETTE_PRESET_NAMES.map((palettePreset) => ({
283
- value: palettePreset,
284
- label: palettePreset,
285
- description:
286
- [
287
- palettePreset === current ? "current" : undefined,
288
- palettePreset === "custom" ? "per-segment colors from settings JSON" : undefined,
289
- ]
290
- .filter((part): part is string => part !== undefined)
291
- .join(" • ") || undefined,
292
- }));
293
- const selectedIndex = PALETTE_PRESET_NAMES.indexOf(current);
294
- const result = await ctx.ui.custom<PalettePreset | null>((tui, theme, _keybindings, done) => {
295
- const container = new Container();
296
- container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
297
- const title = new Text("", 1, 0);
298
- container.addChild(title);
299
- const list = new SelectList(items, Math.min(items.length, 10), {
300
- selectedPrefix: (text) => theme.fg("accent", text),
301
- selectedText: (text) => theme.fg("accent", text),
302
- description: (text) => theme.fg("muted", text),
303
- scrollInfo: (text) => theme.fg("dim", text),
304
- noMatch: (text) => theme.fg("warning", text),
305
- });
306
- list.setSelectedIndex(selectedIndex);
307
- list.onSelectionChange = (item) => {
308
- options.preview?.(item.value as PalettePreset, ctx);
309
- };
310
- list.onSelect = (item) => done(item.value as PalettePreset);
311
- list.onCancel = () => done(null);
312
- container.addChild(list);
313
- const hint = new Text("", 1, 0);
314
- container.addChild(hint);
315
- container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
316
- const updateThemedText = () => {
317
- title.setText(theme.fg("accent", theme.bold(`Palette preset (current: ${current})`)));
318
- hint.setText(theme.fg("dim", "↑↓ preview • enter apply • esc cancel"));
319
- };
320
- updateThemedText();
321
-
322
- return {
323
- render: (width: number) => container.render(width),
324
- invalidate() {
325
- container.invalidate();
326
- updateThemedText();
327
- },
328
- handleInput(data: string) {
329
- list.handleInput(data);
330
- tui.requestRender();
331
- },
332
- };
296
+ const { runLiveChoice } = await import("@narumitw/pi-tui-kit");
297
+ const result = await runLiveChoice(ctx, {
298
+ title: `Palette preset (current: ${current})`,
299
+ items: PALETTE_PRESET_NAMES.map((palettePreset) => ({
300
+ id: palettePreset,
301
+ label: palettePreset,
302
+ description: palettePreset === "custom" ? "per-segment colors from settings JSON" : undefined,
303
+ })),
304
+ currentItemId: current,
305
+ initialItemId: current,
306
+ viewportSize: Math.min(PALETTE_PRESET_NAMES.length, 10),
307
+ hint: "close",
308
+ navigationLabel: "preview",
309
+ confirmLabel: "apply",
310
+ onSelectionChange: ({ item }) => options.preview?.(item.id, ctx),
311
+ signal: owner.signal,
312
+ isCurrent: owner.isCurrent,
333
313
  });
334
- return result ?? undefined;
314
+ return result.kind === "selected" ? result.itemId : undefined;
335
315
  }
336
316
 
337
317
  function applyInformationProfile(