pi-zentui 0.10.1 → 0.12.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 CHANGED
@@ -21,7 +21,7 @@ Zentui brings two popular aesthetics to Pi:
21
21
  - `on branch` — git branch with icon
22
22
  - `[!?↑]` — git status indicators (modified, untracked, ahead/behind, stashed, etc.)
23
23
  - `via v5.5.0` — runtime detection with version and Starship-style Nerd Font runtime/language modules
24
- - Optional segments (off by default): `user@host`, current time, OS icon, session duration, and the **project package version** (e.g. `package.json` → `0.6.0`) — distinct from the runtime segment, which shows the installed toolchain
24
+ - Optional segments (off by default): session name, `user@host`, current time, OS icon, session duration, and the **project package version** (e.g. `package.json` → `0.6.0`) — distinct from the runtime segment, which shows the installed toolchain
25
25
  - Right side shows context usage, token counts, and cost
26
26
  - Built-in footer segments can be shown or hidden individually from `/zentui`
27
27
  - Fully custom Starship-style layout via a `footerFormat` template string — see [Footer Format Template](#footer-format-template)
@@ -194,6 +194,7 @@ Default config values — copy this and change any value you want:
194
194
  },
195
195
  "colors": {
196
196
  "cwd": "bold cyan",
197
+ "sessionName": "bold green",
197
198
  "gitBranch": "bold purple",
198
199
  "gitStatus": "bold red",
199
200
  "contextNormal": "bright-black",
@@ -236,6 +237,7 @@ Default config values — copy this and change any value you want:
236
237
  },
237
238
  "footerSegments": {
238
239
  "cwd": true,
240
+ "sessionName": true,
239
241
  "gitBranch": true,
240
242
  "gitStatus": true,
241
243
  "gitCounts": false,
@@ -275,7 +277,7 @@ Default config values — copy this and change any value you want:
275
277
 
276
278
  - Style values can be Starship/terminal strings (`bold purple`, `fg:202`, `#89b` / `#89b4fa`, `bg:blue fg:bright-green`) or Pi theme tokens (`accent`, `borderMuted`, `thinkingHigh`). Short `#rgb` hex values expand to `#rrggbb`.
277
279
  - `projectRefreshIntervalMs`: project status polling interval; `0` disables polling. Values `1..4999` clamp up to `5000` (minimum 5s); invalid/non-finite values fall back to `30000`.
278
- - `contextStyle`: `text` (default), `gauge`, or `text+gauge` for the context segment.
280
+ - `contextStyle`: `text` (default), `gauge`, or `text+gauge` for the context segment. Context usage refreshes during assistant streaming; token and cost totals remain canonical and finalize at turn boundaries.
279
281
  - `separator`: controls the default footer layout and extension-status connectors: `pipe` (default, ` | `), `dot` (` · `), `chevron` (` › `), or `none` (one space). Cycle it from the `/zentui` **Layout** tab. This selects the separator glyph; `colors.separator` controls its color. Custom `footerFormat` literals and `$sep` keep their existing behavior.
280
282
  - `contextThresholds`: `{ warning, error }` percentages (default `70` / `90`) that select contextNormal / contextWarning / contextError colors.
281
283
  - `pathDisplay`: controls how the cwd/`$cwd` path is shown. `mode` is `basename` (default, last segment only) or `full` (path with home contracted to `~`). In `full` mode, `depth` keeps only the last N trailing directories (`0` = entire path after `~`, max `5`); when parents are dropped the path is prefixed with `…/` (Starship-style). The `/zentui` **Layout** tab cycles path mode and path depth (`0`–`5`; depth is ignored for basename). Example: `~/Projects/foo/bar` with `depth: 2` → `…/foo/bar`.
@@ -283,7 +285,7 @@ Default config values — copy this and change any value you want:
283
285
  - `icons`: every shown icon key is configurable; omit any key to use the Zentui default. `icons.mode` is `auto` | `nerd` | `ascii` (default `auto`, same glyphs as nerd). ASCII mode swaps in plain fallbacks for statusline icons and runtime symbols — useful without a Nerd Font. Custom per-icon strings always win over mode defaults. Custom `icons.os` always wins; when left at the mode default, Zentui maps the OS icon by platform. `rail` sets the vertical glyph drawn as the left rail of the active editor frame and previous user messages when `copyFriendly` is disabled (default `│`; any single Unicode vertical or block glyph). `editorPrompt` controls an optional copy-friendly editor prompt glyph; the default is `""` so copy-friendly mode stays rail-free.
284
286
  - `colorSources`: `theme` maps styles through Pi theme tokens; `terminal` emits terminal colors. `/zentui` switches these sources; manual JSON controls specific style values.
285
287
  - `features`: `editor` enables Zentui's custom editor, selector borders, and previous-message chrome. `statusLine` enables Zentui's custom footer/status line. `copyFriendly` hides editor and previous-message rail glyphs so native terminal selection copies less chrome. All three can be changed from `/zentui` or direct slash-command arguments.
286
- - `footerSegments`: show or hide individual built-in footer segments (`cwd`, `gitBranch`, `gitStatus`, `gitCounts`, `gitCommit`, `gitMetrics`, `runtime`, `packageVersion`, `sessionDuration`, `username`, `time`, `os`, `context`, `tokens`, `cost`). Toggle them from the `Built-in segments` tab in `/zentui`.
288
+ - `footerSegments`: show or hide individual built-in footer segments (`cwd`, `sessionName`, `gitBranch`, `gitStatus`, `gitCounts`, `gitCommit`, `gitMetrics`, `runtime`, `packageVersion`, `sessionDuration`, `username`, `time`, `os`, `context`, `tokens`, `cost`). Toggle them from the `Built-in segments` tab in `/zentui`.
287
289
  - `footerFormat`: optional Starship-style template string that fully controls the footer layout. When set, it overrides `footerSegments`. See [Footer Format Template](#footer-format-template) below. The `/zentui` **Layout** tab configures context style, separator, path display mode/depth, branch length, and icon mode; set or clear custom formats with `/zentui format`.
288
290
  - `gitCommit`: Starship [`git_commit`](https://starship.rs/config/#git-commit)-style options for the `gitCommit` footer segment. `hashLength` (default `7`, clamped to `4`–`40`) controls the short-hash display length. `onlyDetached` (default `true`) shows the hash mainly on detached HEAD. `showTag` (default `true`) appends an exact-match tag (`git describe --tags --exact-match HEAD`). The tag probe piggybacks on the existing git refresh — it only runs when both the segment and `showTag` are on, and misses/failures degrade silently.
289
291
  - `gitMetrics`: Starship [`git_metrics`](https://starship.rs/config/#git-metrics)-style options for the `gitMetrics` footer segment. Uses `git diff HEAD --numstat` (staged + unstaged combined — the Starship “total dirty” view) to show aggregate `+added −deleted` line counts. `onlyNonzero` (default `true`) omits each zero component independently and hides the segment entirely at `0/0`. `ignoreSubmodules` (default `false`) adds `--ignore-submodules=all`. The numstat diff piggybacks on the existing git refresh and uses a hard 2s timeout; a metrics-only failure degrades silently without discarding fresh branch/status data. On very large monorepos the diff may lag or be omitted on timeout.
@@ -304,7 +306,7 @@ A second `$fill` creates a **centered middle zone** — content between the two
304
306
 
305
307
  ```json
306
308
  {
307
- "footerFormat": "$os $username $cwd( on $git_branch)( $git_status)( via $runtime)$fill($context)($sep$tokens)($sep$cost)($sep$time)"
309
+ "footerFormat": "$os $username $cwd($sep$session_name)( on $git_branch)( $git_status)( via $runtime)$fill($context)($sep$tokens)($sep$cost)($sep$time)"
308
310
  }
309
311
  ```
310
312
 
@@ -321,6 +323,7 @@ Center the branch between directory and cost:
321
323
  | Token | Aliases | Renders |
322
324
  | ------------------- | ------------ | ------------------------------------------------------------------- |
323
325
  | `$cwd` | `$directory` | current directory |
326
+ | `$session_name` | | current Pi session name |
324
327
  | `$git_branch` | `$branch` | git branch with icon |
325
328
  | `$git_status` | `$status` | `[!?↑]` status block |
326
329
  | `$git_state` | `$state` | `REBASING` / `MERGING` / … (optional `n/m`) |
@@ -354,6 +357,7 @@ Center the branch between directory and cost:
354
357
  - Literal text (`on branch`, `using`, `\|`, spaces) is rendered verbatim — you control all spacing.
355
358
  - Each variable renders its core value only (no `on`/`via` prefixes); add those words as literal text.
356
359
  - Conditional groups: wrap optional pieces in parentheses, e.g. `$cwd( on $git_branch)($git_status)$fill($context)`. If every `$var` inside a group is empty, the whole group (including its literals) is dropped.
360
+ - `$session_name` is available whenever `footerFormat` is set, independently of `footerSegments.sessionName`; use a conditional group such as `($sep$session_name)` so unnamed sessions leave no separator.
357
361
  - Unknown `$variables` render empty.
358
362
  - Set or clear at runtime: `/zentui format "<template>"` and `/zentui format clear`.
359
363
 
@@ -409,7 +413,7 @@ Mouse wheel scrolling is enabled by default when the fixed editor is on. Disable
409
413
 
410
414
  ## Requirements
411
415
 
412
- - [Pi](https://pi.dev) coding agent 0.80 or newer
416
+ - [Pi](https://pi.dev) coding agent 0.80.3 or newer
413
417
  - A [Nerd Font](https://www.nerdfonts.com/) for icons (or set `icons.mode` to `"ascii"`)
414
418
 
415
419
  ## Development
@@ -66,6 +66,7 @@ export type UiFeaturesConfig = {
66
66
 
67
67
  export type FooterSegmentsConfig = {
68
68
  cwd: boolean;
69
+ sessionName: boolean;
69
70
  gitBranch: boolean;
70
71
  gitStatus: boolean;
71
72
  gitCounts: boolean;
@@ -132,6 +133,7 @@ export type PolishedTuiConfig = {
132
133
  icons: ResolvedIcons;
133
134
  colors: {
134
135
  cwd: ColorSpec;
136
+ sessionName: ColorSpec;
135
137
  gitBranch: ColorSpec;
136
138
  gitStatus: ColorSpec;
137
139
  contextNormal: ColorSpec;
@@ -177,6 +179,7 @@ export type PolishedTuiConfig = {
177
179
  */
178
180
  export const FOOTER_FORMAT_VARIABLES = [
179
181
  "cwd",
182
+ "session_name",
180
183
  "git_branch",
181
184
  "git_status",
182
185
  "git_state",
@@ -229,6 +232,7 @@ export const defaultConfig: PolishedTuiConfig = {
229
232
  },
230
233
  colors: {
231
234
  cwd: "bold cyan",
235
+ sessionName: "bold green",
232
236
  gitBranch: "bold purple",
233
237
  gitStatus: "bold red",
234
238
  contextNormal: "bright-black",
@@ -260,6 +264,7 @@ export const defaultConfig: PolishedTuiConfig = {
260
264
  },
261
265
  footerSegments: {
262
266
  cwd: true,
267
+ sessionName: true,
263
268
  gitBranch: true,
264
269
  gitStatus: true,
265
270
  gitCounts: false,
@@ -431,6 +436,7 @@ function normalizeIconOverrides(record: Record<string, unknown>): Partial<IconGl
431
436
  function normalizeColors(record: Record<string, unknown>): Partial<PolishedTuiConfig["colors"]> {
432
437
  return definedColors({
433
438
  cwd: colorValue(record, "cwd") ?? colorValue(record, "cwdText"),
439
+ sessionName: colorValue(record, "sessionName"),
434
440
  gitBranch: colorValue(record, "gitBranch") ?? colorValue(record, "git"),
435
441
  gitStatus: colorValue(record, "gitStatus"),
436
442
  contextNormal: colorValue(record, "contextNormal"),
@@ -482,6 +488,7 @@ function normalizeUiFeatures(record: Record<string, unknown>): UiFeaturesConfig
482
488
  function normalizeFooterSegments(record: Record<string, unknown>): FooterSegmentsConfig {
483
489
  return {
484
490
  cwd: footerSegmentValue(record, "cwd"),
491
+ sessionName: footerSegmentValue(record, "sessionName"),
485
492
  gitBranch: footerSegmentValue(record, "gitBranch"),
486
493
  gitStatus: footerSegmentValue(record, "gitStatus"),
487
494
  gitCounts: footerSegmentValue(record, "gitCounts"),
@@ -593,6 +600,7 @@ function isUiFeatureKey(value: string): value is keyof UiFeaturesConfig {
593
600
  function isFooterSegmentKey(value: string): value is keyof FooterSegmentsConfig {
594
601
  return (
595
602
  value === "cwd" ||
603
+ value === "sessionName" ||
596
604
  value === "gitBranch" ||
597
605
  value === "gitStatus" ||
598
606
  value === "gitCounts" ||
@@ -2,7 +2,11 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
3
  import type { PolishedTuiConfig, SeparatorStyle } from "./config";
4
4
  import { FOOTER_FORMAT_ALIASES } from "./config";
5
- import { collectExtensionStatusSegments, type ExtensionStatusSegment } from "./extension-status";
5
+ import {
6
+ collectExtensionStatusSegments,
7
+ type ExtensionStatusSegment,
8
+ sanitizeExtensionStatusText,
9
+ } from "./extension-status";
6
10
  import { parseFooterFormat, renderFormatSplit, stripOrphanSeparators } from "./footer-format";
7
11
  import {
8
12
  buildContextDisplayLabel,
@@ -19,6 +23,7 @@ import {
19
23
  formatUsernameHostLabel,
20
24
  } from "./format";
21
25
  import { resolveRuntimeSymbol } from "./icons";
26
+ import type { LiveContextOverride } from "./live-context";
22
27
  import type { FooterState } from "./state";
23
28
  import { renderStyleForSource } from "./style";
24
29
 
@@ -155,6 +160,7 @@ export function installFooter(
155
160
  setRequestRender: (fn: (() => void) | undefined) => void;
156
161
  scheduleProjectRefresh: (ctx: ExtensionContext) => void;
157
162
  setExtensionStatusesGetter?: (fn: (() => ReadonlyMap<string, string>) | undefined) => void;
163
+ getLiveContext?: () => LiveContextOverride | undefined;
158
164
  },
159
165
  ): void {
160
166
  ctx.ui.setFooter((tui, theme, footerData) => {
@@ -193,19 +199,35 @@ export function installFooter(
193
199
  depth: config.pathDisplay.depth,
194
200
  }),
195
201
  );
202
+ const needsSessionName = config.footerFormat
203
+ ? /(?:\$session_name\b|\$\{session_name\})/.test(config.footerFormat)
204
+ : config.footerSegments.sessionName;
205
+ const sessionName = needsSessionName
206
+ ? sanitizeExtensionStatusText(ctx.sessionManager.getSessionName() ?? "")
207
+ : "";
208
+ const sessionNameLabel = sessionName
209
+ ? renderStyleForSource(theme, colorSource, config.colors.sessionName, sessionName)
210
+ : "";
211
+ const builtInSessionNameLabel = sessionNameLabel ? `in ${sessionNameLabel}` : "";
196
212
  const branch = state.branch;
197
213
  const branchText = branch
198
214
  ? formatGitBranchText(branch, config.gitBranch.maxLength)
199
215
  : undefined;
200
216
  const contextUsage = ctx.getContextUsage();
217
+ const liveContext = hooks.getLiveContext?.();
201
218
  const contextWindow = ctx.model?.contextWindow ?? contextUsage?.contextWindow;
219
+ const useLiveContext =
220
+ liveContext !== undefined && contextWindow !== undefined && contextWindow > 0;
221
+ const contextPercent = useLiveContext
222
+ ? (liveContext.tokens / contextWindow) * 100
223
+ : contextUsage?.percent;
202
224
  const contextLabel = buildContextDisplayLabel({
203
- percent: contextUsage?.percent,
225
+ percent: contextPercent,
204
226
  contextWindow,
205
227
  style: config.contextStyle,
206
228
  asciiGauge: iconMode === "ascii",
207
229
  });
208
- const tier = contextColorTier(contextUsage?.percent, config.contextThresholds);
230
+ const tier = contextColorTier(contextPercent, config.contextThresholds);
209
231
  const contextColor =
210
232
  tier === "error"
211
233
  ? config.colors.contextError
@@ -255,6 +277,8 @@ export function installFooter(
255
277
  switch (canonical) {
256
278
  case "cwd":
257
279
  return cwdLabel;
280
+ case "session_name":
281
+ return sessionNameLabel;
258
282
  case "git_branch":
259
283
  return branchText
260
284
  ? gitIcon
@@ -483,6 +507,7 @@ export function installFooter(
483
507
  osSegment,
484
508
  usernameSegment,
485
509
  config.footerSegments.cwd ? cwdLabel : "",
510
+ config.footerSegments.sessionName ? builtInSessionNameLabel : "",
486
511
  branchLabel,
487
512
  gitCommitLabel,
488
513
  gitMetricsLabel,
@@ -41,6 +41,7 @@ import {
41
41
  import { installFooter } from "./footer";
42
42
  import { buildSessionDurationLabel, invalidateUsageTotalsCache } from "./format";
43
43
  import { emptyGitStatus, readGitStatus } from "./git";
44
+ import { LiveContextController } from "./live-context";
44
45
  import { readPackageVersionResult } from "./package-version";
45
46
  import {
46
47
  createProjectRefreshScheduler,
@@ -113,6 +114,7 @@ export default function (pi: ExtensionAPI) {
113
114
  const refresh = () => {
114
115
  if (sessionLifecycle.isCurrent()) requestFooterRender?.();
115
116
  };
117
+ const liveContext = new LiveContextController(sessionLifecycle, refresh);
116
118
  const getActiveTheme = () => activeTheme;
117
119
  const getCurrentConfig = () => currentConfig;
118
120
  const getThinkingLevel = () =>
@@ -330,6 +332,7 @@ export default function (pi: ExtensionAPI) {
330
332
  setExtensionStatusesGetter(fn) {
331
333
  getActiveExtensionStatuses = fn ?? (() => new Map());
332
334
  },
335
+ getLiveContext: () => liveContext.get(),
333
336
  });
334
337
  footerInstalled = true;
335
338
  stopProjectRefresh();
@@ -442,6 +445,7 @@ export default function (pi: ExtensionAPI) {
442
445
 
443
446
  pi.on("session_start", async (_event, ctx) => {
444
447
  sessionLifecycle.start();
448
+ liveContext.clear();
445
449
  state.sessionStartEpoch = Date.now();
446
450
  invalidateUsageTotalsCache();
447
451
  lastProjectCwd = undefined;
@@ -510,6 +514,7 @@ export default function (pi: ExtensionAPI) {
510
514
  });
511
515
 
512
516
  pi.on("session_shutdown", async (_event, ctx) => {
517
+ liveContext.clear();
513
518
  cleanupUi(ctx);
514
519
  });
515
520
 
@@ -518,12 +523,45 @@ export default function (pi: ExtensionAPI) {
518
523
  refreshInteractiveState(ctx, true);
519
524
  };
520
525
 
521
- pi.on("agent_start", syncInteractiveState);
522
- pi.on("agent_end", syncInteractiveAndProjectState);
523
- pi.on("model_select", syncInteractiveState);
526
+ pi.on("agent_start", (event, ctx) => {
527
+ liveContext.clear();
528
+ syncInteractiveState(event, ctx);
529
+ });
530
+ pi.on("agent_end", (event, ctx) => {
531
+ liveContext.clear();
532
+ syncInteractiveAndProjectState(event, ctx);
533
+ });
534
+ pi.on("model_select", (event, ctx) => {
535
+ liveContext.clear();
536
+ syncInteractiveState(event, ctx);
537
+ });
524
538
  pi.on("thinking_level_select", syncInteractiveState);
525
- pi.on("message_end", syncInteractiveAndProjectStateWithUsage);
539
+ pi.on("session_info_changed", syncInteractiveState);
540
+ pi.on("message_update", (event) => {
541
+ liveContext.update(event.message);
542
+ });
543
+ pi.on("message_end", (event, ctx) => {
544
+ // Pi notifies extensions before persisting a successful message, so retain its live
545
+ // context until agent_end; failed messages clear immediately instead of showing stale usage.
546
+ if (
547
+ event.message.role === "assistant" &&
548
+ (event.message.stopReason === "error" || event.message.stopReason === "aborted")
549
+ ) {
550
+ liveContext.clear();
551
+ }
552
+ syncInteractiveAndProjectStateWithUsage(event, ctx);
553
+ });
554
+ pi.on("tool_execution_start", (event, ctx) => {
555
+ liveContext.clear();
556
+ syncInteractiveState(event, ctx);
557
+ });
526
558
  pi.on("tool_execution_end", syncInteractiveAndProjectState);
527
- pi.on("session_compact", syncInteractiveAndProjectStateWithUsage);
528
- pi.on("session_tree", syncInteractiveAndProjectStateWithUsage);
559
+ pi.on("session_compact", (event, ctx) => {
560
+ liveContext.clear();
561
+ syncInteractiveAndProjectStateWithUsage(event, ctx);
562
+ });
563
+ pi.on("session_tree", (event, ctx) => {
564
+ liveContext.clear();
565
+ syncInteractiveAndProjectStateWithUsage(event, ctx);
566
+ });
529
567
  }
@@ -0,0 +1,75 @@
1
+ import type { AssistantMessage, Usage } from "@earendil-works/pi-ai";
2
+ import type { SessionLifecycle } from "./session-lifecycle";
3
+
4
+ export type LiveContextOverride = {
5
+ tokens: number;
6
+ };
7
+
8
+ function usageComponent(value: unknown): number {
9
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
10
+ }
11
+
12
+ export function calculateLiveContextTokens(usage: Usage | undefined): number | undefined {
13
+ if (!usage) return undefined;
14
+ const totalTokens = usageComponent(usage.totalTokens);
15
+ if (totalTokens > 0) return totalTokens;
16
+ const calculated =
17
+ usageComponent(usage.input) +
18
+ usageComponent(usage.output) +
19
+ usageComponent(usage.cacheRead) +
20
+ usageComponent(usage.cacheWrite);
21
+ return calculated > 0 ? calculated : undefined;
22
+ }
23
+
24
+ export function liveContextFromMessage(message: unknown): LiveContextOverride | undefined {
25
+ if (!message || typeof message !== "object") return undefined;
26
+ const assistant = message as Partial<AssistantMessage>;
27
+ if (assistant.role !== "assistant") return undefined;
28
+ if (assistant.stopReason === "error" || assistant.stopReason === "aborted") return undefined;
29
+ const tokens = calculateLiveContextTokens(assistant.usage);
30
+ return tokens === undefined ? undefined : { tokens };
31
+ }
32
+
33
+ export class LiveContextController {
34
+ private readonly lifecycle: SessionLifecycle;
35
+ private readonly requestRender: () => void;
36
+ private override: LiveContextOverride | undefined;
37
+ private cancelScheduledRender: (() => void) | undefined;
38
+ private scheduledGeneration: number | undefined;
39
+
40
+ constructor(lifecycle: SessionLifecycle, requestRender: () => void) {
41
+ this.lifecycle = lifecycle;
42
+ this.requestRender = requestRender;
43
+ }
44
+
45
+ get(): LiveContextOverride | undefined {
46
+ return this.override;
47
+ }
48
+
49
+ update(message: unknown): boolean {
50
+ const next = liveContextFromMessage(message);
51
+ if (!next || !this.lifecycle.isCurrent()) return false;
52
+ this.override = next;
53
+ const generation = this.lifecycle.currentGeneration();
54
+ if (this.cancelScheduledRender && this.scheduledGeneration !== generation) {
55
+ this.cancelScheduledRender = undefined;
56
+ this.scheduledGeneration = undefined;
57
+ }
58
+ if (!this.cancelScheduledRender) {
59
+ this.scheduledGeneration = generation;
60
+ this.cancelScheduledRender = this.lifecycle.defer(() => {
61
+ this.cancelScheduledRender = undefined;
62
+ this.scheduledGeneration = undefined;
63
+ if (this.override) this.requestRender();
64
+ }, 250);
65
+ }
66
+ return true;
67
+ }
68
+
69
+ clear(): void {
70
+ this.override = undefined;
71
+ this.cancelScheduledRender?.();
72
+ this.cancelScheduledRender = undefined;
73
+ this.scheduledGeneration = undefined;
74
+ }
75
+ }
@@ -18,14 +18,21 @@ export class SessionLifecycle {
18
18
  return this.active && generation === this.generation;
19
19
  }
20
20
 
21
- defer(callback: () => void): void {
22
- if (!this.active) return;
21
+ defer(callback: () => void, delayMs = 0): () => void {
22
+ if (!this.active) return () => {};
23
23
  const generation = this.generation;
24
+ let canceled = false;
24
25
  const timeout = setTimeout(() => {
25
26
  this.timeouts.delete(timeout);
26
- if (this.isCurrent(generation)) callback();
27
- }, 0);
27
+ if (!canceled && this.isCurrent(generation)) callback();
28
+ }, delayMs);
28
29
  this.timeouts.add(timeout);
30
+ return () => {
31
+ if (canceled) return;
32
+ canceled = true;
33
+ clearTimeout(timeout);
34
+ this.timeouts.delete(timeout);
35
+ };
29
36
  }
30
37
 
31
38
  queueMicrotask(callback: () => void): () => void {
@@ -124,6 +124,7 @@ const featureSettingDescriptions: Record<FeatureSettingId, string> = {
124
124
 
125
125
  const footerSegmentSettingLabels: Record<FooterSegmentSettingId, string> = {
126
126
  cwd: "Current directory",
127
+ sessionName: "Session name",
127
128
  gitBranch: "Git branch",
128
129
  gitStatus: "Git status",
129
130
  gitCounts: "Git counts",
@@ -142,6 +143,7 @@ const footerSegmentSettingLabels: Record<FooterSegmentSettingId, string> = {
142
143
 
143
144
  const footerSegmentSettingDescriptions: Record<FooterSegmentSettingId, string> = {
144
145
  cwd: "Show or hide the current working directory segment on the left.",
146
+ sessionName: "Show or hide the current Pi session name on the left, after the current directory.",
145
147
  gitBranch: "Show or hide the git branch name on the left.",
146
148
  gitStatus: "Show or hide git status icons and ahead/behind markers.",
147
149
  gitCounts:
@@ -207,6 +209,7 @@ function isFeatureSettingId(value: string): value is FeatureSettingId {
207
209
  function isFooterSegmentSettingId(value: string): value is FooterSegmentSettingId {
208
210
  return (
209
211
  value === "cwd" ||
212
+ value === "sessionName" ||
210
213
  value === "gitBranch" ||
211
214
  value === "gitStatus" ||
212
215
  value === "gitCounts" ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-zentui",
3
- "version": "0.10.1",
3
+ "version": "0.12.0",
4
4
  "description": "A Starship-inspired statusline and Opencode-style TUI for Pi.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,9 +35,9 @@
35
35
  "prepublishOnly": "npm run verify && npm run pack:check"
36
36
  },
37
37
  "peerDependencies": {
38
- "@earendil-works/pi-ai": ">=0.80",
39
- "@earendil-works/pi-coding-agent": ">=0.80",
40
- "@earendil-works/pi-tui": ">=0.80"
38
+ "@earendil-works/pi-ai": ">=0.80.3",
39
+ "@earendil-works/pi-coding-agent": ">=0.80.3",
40
+ "@earendil-works/pi-tui": ">=0.80.3"
41
41
  },
42
42
  "pi": {
43
43
  "extensions": [