pi-zentui 0.25.0 → 0.26.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
@@ -1,6 +1,6 @@
1
1
  <h1 align="center">Zentui</h1>
2
2
 
3
- <p align="center">A Starship-inspired statusline and Opencode-style TUI for <a href="https://pi.dev">Pi</a>.</p>
3
+ <p align="center">A modular terminal UI for <a href="https://pi.dev">Pi</a>.</p>
4
4
 
5
5
  <p align="center">
6
6
  <a href="https://www.npmjs.com/package/pi-zentui"><img alt="npm version" src="https://shieldcn.dev/npm/pi-zentui.svg?variant=outline" /></a>
@@ -14,13 +14,13 @@
14
14
 
15
15
  ## What is this?
16
16
 
17
- Zentui gives Pi surfaces independently selectable treatments:
17
+ Zentui lets you shape Pi’s interface one component at a time. Customize your editor, messages, progress display, and footer independently—use the pieces you want and leave the rest native.
18
18
 
19
19
  - **Editor** — Opencode, Opencode copy-friendly, Accent Rail, and Minimalist input treatments
20
20
  - **User messages** — framed, framed copy-friendly, compact, and labeled transcript messages
21
21
  - **Thinking (Experimental)** — optional Rail, Tree, or Streaming private thinking renderers, without owning the Working line
22
22
  - **Working line** — optional ownership of Pi's complete in-progress row and settled turn summary
23
- - **Footer** — Pi's native Footer, a Starship-style statusline, or a hidden zero-row Footer
23
+ - **Footer** — Pi's native Footer, a Starship-style statusline, or Hidden (only allowed extension statuses)
24
24
 
25
25
  Editor, User messages, Thinking (Experimental), Working line, and selector borders have independent `enabled` fields. Footer uses one `style`: `native`, `starship`, or `hidden`. Use `/zentui` to configure each component without coupling it to the others.
26
26
 
@@ -91,7 +91,7 @@ pi install git:github.com/lmilojevicc/pi-zentui
91
91
 
92
92
  ## Configure
93
93
 
94
- Run `/zentui` inside Pi to configure Appearance, Editor, User messages, Thinking (Experimental), Working line, and Footer. With Starship selected, Footer contains **Segments →**, **Git →**, and **Extension statuses →** child pages. Use `Tab` and `Shift+Tab` to switch sections; compact help follows your configured selection keys. Every section has a direct route (for example, `/zentui footer`). `/zentui segments`, `/zentui git`, and `/zentui extensions` open the Footer child pages when Starship is active; under Native or Hidden they open Footer with a requires-Starship explanation, without changing style or saved preferences. The configured cancel key returns from a child to Footer; at the top level it closes settings. Extension statuses are published keyed Footer statuses, not extension management or Working line integrations. Inactive options retain their saved preferences. Most changes apply live. Active Streaming can switch live to Rail or Tree, and Rail and Tree can switch live between each other. Entering Streaming from a structural mode, first enable, and re-enable after a live disable require restarting Pi. Configuration is saved to:
94
+ Run `/zentui` inside Pi to configure Appearance, Editor, User messages, Thinking (Experimental), Working line, Footer, and Extension statuses. With Starship selected, Footer contains **Segments →** and **Git →** child pages. Use `Tab` and `Shift+Tab` to switch sections; compact help follows your configured selection keys. Every section has a direct route (for example, `/zentui footer`). `/zentui segments` and `/zentui git` open Footer child pages when Starship is active; under Native or Hidden they open Footer with a requires-Starship explanation. `/zentui extensions` always opens independent default/per-key status visibility, without changing Footer style or saved Starship placement/color preferences. The configured cancel key returns from a child to Footer; at the top level it closes settings. Extension statuses are published keyed Footer statuses, not extension management or Working line integrations. Inactive options retain their saved preferences. Most changes apply live. Active Streaming can switch live to Rail or Tree, and Rail and Tree can switch live between each other. Entering Streaming from a structural mode, first enable, and re-enable after a live disable require restarting Pi. Configuration is saved to:
95
95
 
96
96
  ```text
97
97
  ~/.pi/agent/zentui.json
@@ -106,9 +106,9 @@ Choose **Appearance → Preset** in `/zentui`, or run `/zentui preset <id>`:
106
106
  | `opencode` | Opencode | Starship | Framed |
107
107
  | `opencode-copy-friendly` | Opencode (copy-friendly) | Starship | Framed (copy-friendly) |
108
108
  | `rail` | Accent Rail | Starship | Compact (accent rail) |
109
- | `minimalist` | Minimalist | Hidden (zero rows) | Zentui styling disabled |
109
+ | `minimalist` | Minimalist | Hidden (statuses only) | Zentui styling disabled |
110
110
 
111
- Presets apply once, saving only these component selections. Colors, color sources, style options, icons, Footer segments/formats, selector borders, Working line, and Thinking remain unchanged. Minimalist preserves the dormant message style and leaves Pi's native message presentation alone. Hidden suppresses the Footer; it is not Native.
111
+ Presets apply once, saving only these component selections. Colors, color sources, style options, icons, Footer segments/formats, selector borders, Working line, and Thinking remain unchanged. Minimalist preserves the dormant message style and leaves Pi's native message presentation alone. Hidden suppresses main Footer segments, not allowed extension statuses; it is not Native.
112
112
 
113
113
  The displayed preset is derived from your current selections: individual changes may show **Custom**, and returning to a matching combination restores its name. No `preset` config key is saved or reapplied at startup. Defaults are unchanged and match Opencode. Selecting a preset keeps settings open for further adjustments. Editor installation waits until the panel closes; if editor ownership prevents application, the saved choice may require reloading Pi.
114
114
 
@@ -141,7 +141,7 @@ To adopt **only User messages**, explicitly leave the other default-enabled surf
141
141
  }
142
142
  ```
143
143
 
144
- Disabled/Native leaves Pi or a predecessor in control. Hidden instead intentionally owns zero Footer rows. Ordinary saves snapshot only the edited owner. `/zentui migrate` is a separate confirmed all-owner selection/source/style-option snapshot; it preserves aliases, unknown fields, and shared color inheritance, and never copies generated palettes. See the configuration reference for exact owner color keys and reset behavior.
144
+ Disabled/Native leaves Pi or a predecessor in control. Hidden instead shows only allowed extension statuses below the editor, with no row when empty. Ordinary saves snapshot only the edited owner. `/zentui migrate` is a separate confirmed all-owner selection/source/style-option snapshot; it preserves aliases, unknown fields, and shared color inheritance, and never copies generated palettes. See the configuration reference for exact owner color keys and reset behavior.
145
145
 
146
146
  Detailed reference:
147
147
 
@@ -31,11 +31,11 @@ To adopt only User messages while leaving the other default-enabled surfaces nat
31
31
  }
32
32
  ```
33
33
 
34
- Native releases Zentui's ownership; Hidden deliberately installs a zero-row Footer. Disabling a component preserves its dormant preferences. See [color overrides and inheritance](#component-color-overrides-and-inheritance) and [explicit migration](#compatibility-and-migration) before snapshotting legacy settings.
34
+ Native releases Zentui's ownership; Hidden hides Footer metrics, path, and model segments but keeps allowed extension statuses on one line below the editor. With no nonempty allowed statuses, it renders no rows. Disabling a component preserves its dormant preferences. See [color overrides and inheritance](#component-color-overrides-and-inheritance) and [explicit migration](#compatibility-and-migration) before snapshotting legacy settings.
35
35
 
36
36
  ## `/zentui` settings
37
37
 
38
- The interactive `/zentui` menu is split into six component-oriented sections. Use `Tab` and `Shift+Tab` to switch sections. Selection/Change/Back/Close hints follow injected host keybindings (with older-host defaults when unavailable). Narrow help retains Change, Sections, and Back (on child pages) or Close guidance:
38
+ The interactive `/zentui` menu is split into seven component-oriented sections. Use `Tab` and `Shift+Tab` to switch sections. Selection/Change/Back/Close hints follow injected host keybindings (with older-host defaults when unavailable). Narrow help retains Change, Sections, and Back (on child pages) or Close guidance:
39
39
 
40
40
  1. **Appearance** — component Preset; selector-border enablement, informational fixed style, and colors; icon mode.
41
41
  2. **Editor** — enablement, style, colors, Codex quota, model label, border behavior, viewport indicators, settings for the selected editor style, and a static synthetic preview.
@@ -45,13 +45,13 @@ The interactive `/zentui` menu is split into six component-oriented sections. Us
45
45
  6. **Footer** — Native, Starship, or Hidden. Starship additionally exposes colors, Codex quota, model label, responsive layout, separator, context style, and path display.
46
46
  - **Segments →** — visibility toggles for non-Git Starship segments.
47
47
  - **Git →** — Starship Footer Git segment and probe controls, not Editor Git controls.
48
- - **Extension statuses →** — Starship placement and color controls for active published keyed Footer statuses; not extension management or Working line integrations.
48
+ 7. **Extension statuses** — one Default placement and each observed/saved extension's placement and color. Off controls visibility; positions are saved per Footer mode.
49
49
 
50
- The three Footer child entries appear only with Starship selected. Child headings show their scope (for example, **Footer > Git**). The configured cancel key returns to Footer focused on the originating child entry; at the top level it still closes settings. `Tab` / `Shift+Tab` remain available on child pages to move to the next / previous top-level section relative to Footer. Visiting or backing out of a page does not save settings or change component ownership.
50
+ The two Footer child entries appear only with Starship selected. Child headings show their scope (for example, **Footer > Git**). The configured cancel key returns to Footer focused on the originating child entry; at the top level it still closes settings. `Tab` / `Shift+Tab` remain available on child pages to move to the next / previous top-level section relative to Footer. Visiting or backing out of a page does not save settings or change component ownership.
51
51
 
52
- Editor, User messages, Thinking (Experimental), and Working line retain independent configuration. Editor, User-message, and Thinking previews remain visible while their component is disabled. Only the Working-line preview owns an animation timer. Starship-specific rows are shown only while Starship is selected. Footer Color overrides remain available for preconfiguration under every Footer style and say **Saved for Starship** when inactive. Native and Hidden hide the three child entries without changing their saved preferences. Other dormant choices explain their scope without rewriting values. Auto icons assume a Nerd Font without detecting one; ASCII replaces icons only, not all borders or UI glyphs.
52
+ Editor, User messages, Thinking (Experimental), and Working line retain independent configuration. Editor, User-message, and Thinking previews remain visible while their component is disabled. Only the Working-line preview owns an animation timer. Footer’s Starship-specific rows are shown only while Starship is selected; Extension statuses offers the same unprefixed placement/color rows in every Footer style. Native saves dormant Starship position/color preferences without changing Pi's layout. Footer Color overrides remain available for preconfiguration under every Footer style and say **Saved for Starship** when inactive. Native and Hidden hide the two child entries without changing their saved preferences. Other dormant choices explain their scope without rewriting values. Auto icons assume a Nerd Font without detecting one; ASCII replaces icons only, not all borders or UI glyphs.
53
53
 
54
- Free-form values such as custom formats, Opencode metadata formats, and inactive extension keys remain JSON-only. Component raw colors are editable through each component’s **Color overrides** action, with explicit **Reset / inherit**. Working-line speed accepts validated custom milliseconds in `/zentui`.
54
+ Free-form values such as custom formats, Opencode metadata formats, and previously unseen, unsaved extension keys remain JSON-only. Component raw colors are editable through each component’s **Color overrides** action, with explicit **Reset / inherit**. Working-line speed accepts validated custom milliseconds in `/zentui`.
55
55
 
56
56
  Every section and Footer child page has a direct route and completion:
57
57
 
@@ -67,7 +67,7 @@ Every section and Footer child page has a direct route and completion:
67
67
  /zentui extensions
68
68
  ```
69
69
 
70
- `/zentui segments`, `/zentui git`, and `/zentui extensions` open the corresponding **Footer > …** child page when Starship is active. Under Native or Hidden, they instead open Footer with a requires-Starship explanation; they do not show active child controls, enable Starship, or write configuration.
70
+ `/zentui segments` and `/zentui git` open the corresponding **Footer > …** child page when Starship is active. Under Native or Hidden, they instead open Footer with a requires-Starship explanation; they do not show active child controls, enable Starship, or write configuration. `/zentui extensions` always opens the independent Extension statuses section.
71
71
 
72
72
  `messages` and `thinking-steps` remain section aliases; Footer also accepts the aliases below. Useful slash-command shortcuts:
73
73
 
@@ -115,16 +115,55 @@ These are one-time, atomic selection patches, not ongoing profiles. Only the lis
115
115
 
116
116
  **Custom** is a derived display state, not a selectable preset or a saved key. Matching checks only the listed selection leaves, not colors/options or current runtime ownership; Minimalist ignores dormant message style. Unsupported active selected styles do not match. Hand-editing or individually changing a selection can show Custom; returning to a matching combination restores the preset label. No `preset` config key is used, and nothing is automatically reapplied on startup. New-install defaults remain unchanged and match Opencode.
117
117
 
118
- The existing Minimalist editor stays enabled with its saved metadata/options. Disabled User messages releases only Zentui styling, leaving native or predecessor rendering intact. Hidden installs an owned zero-row Footer, whereas Native releases Zentui's Footer to Pi or a predecessor; `/zentui statusline disable` still selects Native, not Hidden.
118
+ The existing Minimalist editor stays enabled with its saved metadata/options. Disabled User messages releases only Zentui styling, leaving native or predecessor rendering intact. Hidden owns a status-only Footer (no rows when no allowed statuses remain), whereas Native releases Zentui's Footer to Pi or a predecessor; `/zentui statusline disable` still selects Native, not Hidden.
119
119
 
120
120
  Selecting presets keeps the settings panel open and preserves its focus. Config, Footer, and User messages update immediately; editor installation waits until the panel closes and Pi restores the draft. Only the latest editor settings are reconciled on exit, and shutdown cancels pending installation. With public editor-text APIs available, opening settings expands nonempty drafts before Pi snapshots them, preserving collapsed paste contents; this can move the cursor to the end and add an undo step. Empty drafts are untouched. Older hosts without these APIs retain Pi's existing draft-restoration behavior. Saves fail without changing active settings or overwriting corrupt/unreadable JSON. Live application reconciles only Editor, User messages, Footer, and dependent timers. Editor ownership restrictions are reported as saved-but-not-applied/reload-required; Footer host failures retain existing fail-open behavior. Direct preset commands also save in non-TUI modes without installing TUI components. Unknown IDs, missing IDs, and extra arguments do not change settings.
121
121
 
122
+ ### Extension-status visibility
123
+
124
+ `/zentui extensions` is available with Native, Hidden, or Starship Footer. It shows **Default placement**, then each observed or saved extension's **placement** and **color** (Original, Zentui). Both placement controls offer only Left, Middle, Right, Off, in that order. No separate visibility rows or mode prefixes are shown. Unset or inherited placements display their effective position or Off, not a selectable Default value. Off hides statuses via `components.extensionStatuses`; a positive per-extension placement explicitly shows that key even when the default is Off. Choices save atomically; merely opening the panel does not rewrite config:
125
+
126
+ ```json
127
+ {
128
+ "components": {
129
+ "extensionStatuses": {
130
+ "defaultVisibility": "show",
131
+ "visibility": { "github-pr": "hide" }
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ Off suppresses observed publications through Pi's public `setStatus` method. A visible choice passes the original text through; the current Footer still controls rendering. Hidden renders nonempty allowed statuses on one minimal line below the editor, preserving incoming colors by default and Pi's key ordering within each placement, with safe single-line text and width truncation. Default Off hides inherited keys; per-key Off hides that key. No remaining statuses means no blank row. Starship's local `off` placement still omits a shown status only in Starship, never in Hidden. Starship placement/color preserve their existing Footer-owned preferences; they do not route or recolor Hidden or native/third-party footers. Native positive placements save dormant Starship preferences and Show visibility but never move Pi's native statuses. Historical Starship `off` settings never become global Off in Native or Hidden.
138
+
139
+ With Hidden selected, default placement starts at Left. To restore inheritance in config, remove the key's visibility and Hidden placement overrides; its color can remain saved. These sparse preferences live in `components.extensionStatuses.hidden`, independently of Starship and visibility:
140
+
141
+ ```json
142
+ {
143
+ "components": {
144
+ "extensionStatuses": {
145
+ "hidden": {
146
+ "defaultPlacement": "left",
147
+ "placements": { "github-pr": "right", "sync": "middle" },
148
+ "colorModes": { "github-pr": "zentui" }
149
+ }
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ Each color under Hidden offers Original (the default) or Zentui. Original keeps the extension's supplied styling and safe links. Zentui shows plain text in the active Pi theme's `muted` color, independently of Starship's palette and color-source settings. Choices are saved in `components.extensionStatuses.hidden.colorModes`; selecting Original removes only that key's override. Color changes do not alter visibility or placement.
156
+
157
+ Left and Right anchor to the edges; Middle is terminal-centered, then clamped between them. Crowded zones share space fairly and truncate safely with at least one separating cell. At widths too narrow for all zones, Left, Middle, then Right take priority. Original mode preserves incoming styles and safe links; resets prevent colors/backgrounds/links from leaking into padding or another status. Off and explicit positions change visibility; no choice changes Footer style. Hidden never inherits Starship's `off`, placement, or colors. Switching Footer style retains both sets of preferences; Native keeps native positioning.
158
+
159
+ Discovery is best-effort and session-scoped: observed raw keys and saved visibility/Hidden/Starship keys remain editable, with already-owned Starship or Hidden Footer data supplementing discovery only, never suppression/replay provenance. Hidden also applies visibility to those provider statuses when rendering; it does not republish them. Zentui never installs a Footer just to discover statuses. **No observed statuses** does not mean no extensions are running. Publications before observation, cached setters, separate UI contexts, and nondelegating successor extensions may bypass these controls. Unsupported/frozen setter shapes fail open. Cleanup restores only safely owned suppression and does not overwrite a successor setter or replay uncertain stale values. This controls keyed status visibility, not extension enablement or Working-line integrations.
160
+
122
161
  ### Extension-status hyperlinks
123
162
 
124
163
  An extension's **Original** color mode preserves SGR styling and HTTP/HTTPS
125
164
  OSC 8 hyperlinks supplied by that extension. For example, set
126
165
  `components.footer.styles.starship.extensionStatuses.colorModes.github-pr` to
127
- `"original"` to retain a PR link from a GitHub status extension. Open it with
166
+ `"original"` to retain a PR link from a GitHub status extension. Hidden's color choice is separate, under `components.extensionStatuses.hidden.colorModes`, and defaults to Original. Open it with
128
167
  your terminal's link-opening gesture. Other URL schemes and unrelated terminal
129
168
  controls (including clipboard, title, and cursor commands) are removed.
130
169
 
@@ -232,6 +271,10 @@ Reference only—not a starter file. Prefer the minimal overrides above. Optiona
232
271
  "tokens": true
233
272
  }
234
273
  },
274
+ "extensionStatuses": {
275
+ "defaultVisibility": "show",
276
+ "visibility": {}
277
+ },
235
278
  "selectorBorders": {
236
279
  "enabled": true,
237
280
  "style": "zentui",
@@ -370,9 +413,9 @@ Reference only—not a starter file. Prefer the minimal overrides above. Optiona
370
413
  - All three modes decorate Pi's private host renderer and are tested on exact Pi versions 0.80.5, 0.82.1, 0.83.0, 0.84.0, 0.84.4, and 0.85.1. Active Streaming can switch live to Rail or Tree, and Rail and Tree can switch live between each other. Entering Streaming from a structural mode, first enable, and re-enable after live disable require restart; live disable restores native thinking. Disabled mode changes only preconfigure.
371
414
  - `components.workingLine.enabled` is the sole Working-line ownership switch. Thinking (Experimental) never enables, configures, or owns the Working line and leaves the existing **Thinking time** option unchanged.
372
415
  - `components.selectorBorders` owns selector-border enablement, fixed `zentui` style, and color source. Disable it for native Pi behavior.
373
- - `components.footer` owns `native | starship | hidden` style selection, color source, model label, and Starship options. Hidden installs an empty component with zero rows.
416
+ - `components.footer` owns `native | starship | hidden` style selection, color source, model label, and Starship options. Hidden hides its main segments, retaining only allowed extension statuses; an empty status line occupies no rows.
374
417
  - Starship's package-version segment reads the project manifest and is distinct from the runtime segment, which reports the installed toolchain.
375
- - Active third-party statuses from `ctx.ui.setStatus()` can be placed left, middle, or right, hidden per key, and assigned independent color modes.
418
+ - `components.extensionStatuses` independently controls Show/Hide for observed `ctx.ui.setStatus()` publications. It also owns Hidden-only placement under `hidden`; Starship retains its own placement and color modes.
376
419
  - The shown `editor*` colors match the default `theme` source. Omit them to preserve source-aware defaults when switching between `theme` and `terminal`.
377
420
 
378
421
  ### Codex account quota
@@ -45,6 +45,27 @@ Center the branch between directory and cost:
45
45
  }
46
46
  ```
47
47
 
48
+ Show model, provider, and active thinking level in the Footer:
49
+
50
+ ```text
51
+ /zentui format "$model $provider( $thinkingLevel)"
52
+ ```
53
+
54
+ Add `$model $provider( $thinkingLevel)` to `components.footer.styles.starship.compactFormat` separately to keep these details in compact layouts. Defaults and the built-in Model info segment are unchanged.
55
+
56
+ To keep metadata on the right, set both templates independently:
57
+
58
+ ```json
59
+ {
60
+ "format": "$cwd( in $session_name)( on $git_branch)( $git_status)( $git_state)( via $runtime)$fill$model($sep$provider)($sep$thinkingLevel)($sep$context)( $auto_compaction)($sep$tokens)( $cache_read)( $cache_write)($sep$cost)( $subscription)",
61
+ "compactFormat": "$cwd$wrap(in $session_name)$wrap(on $git_branch) $git_status$fill$model$wrap_sep($provider)$wrap_sep($thinkingLevel)$wrap_sep$context$wrap_sep$tokens"
62
+ }
63
+ ```
64
+
65
+ These keys belong under `components.footer.styles.starship`. Separate metadata chunks can wrap at narrow widths; a single combined chunk may truncate thinking. Earlier right-hand chunks take priority; project details and later telemetry may be truncated or omitted.
66
+
67
+ Footer configuration never changes the Editor. To hide those details there, independently customize `components.editor.styles.opencode.metadataFormat` or `components.editor.styles.opencode-copy-friendly.metadataFormat`, omitting `$model`, `$provider`, and `$thinking`. Use `" "` for no metadata; `""` restores the default. See [Editor metadata format](./configuration.md#editor-metadata-format).
68
+
48
69
  Set or clear the template at runtime:
49
70
 
50
71
  ```text
@@ -71,6 +92,7 @@ The released flat `footerFormat` and `footerSegments` keys remain accepted only
71
92
  | `$runtime` | | runtime icon and version |
72
93
  | `$model` | | selected Footer model label |
73
94
  | `$provider` | | formatted provider label |
95
+ | `$thinkingLevel` | | current thinking level; empty when unavailable or `off` |
74
96
  | `$package` | | project package version as `is <glyph> <version>` |
75
97
  | `$package_version` | | raw project package version |
76
98
  | `$session_duration` | `$duration` | session running time |
@@ -86,7 +108,7 @@ The released flat `footerFormat` and `footerSegments` keys remain accepted only
86
108
  | `$subscription` | | `(sub)` in subscription mode; otherwise empty |
87
109
  | `$auto_compaction` | | `(auto)` when automatic compaction is enabled |
88
110
  | `$sep` | `$separator` | themed `|` using `colors.separator` |
89
- | `$fill` | — | wide-format layout boundary |
111
+ | `$fill` | — | wide or compact layout boundary |
90
112
 
91
113
  Each variable renders its core value without prose prefixes such as `on` or `via`; add those words as literals.
92
114
 
@@ -138,6 +160,8 @@ These tokens are structural in compact mode: `$wrap` and `$wrap_sep` render no t
138
160
 
139
161
  ## `$fill` behavior
140
162
 
163
+ In the wide template:
164
+
141
165
  | Count | Layout |
142
166
  | ---: | --- |
143
167
  | 0 | everything left-aligned |
@@ -145,7 +169,11 @@ These tokens are structural in compact mode: `$wrap` and `$wrap_sep` render no t
145
169
  | 2 | before first is left, between is truly centered, after second is right |
146
170
  | 3+ | first two count; extras are ignored |
147
171
 
148
- The centered middle zone uses `floor((gap - middle) / 2)`, matching third-party statuses placed in the middle.
172
+ The centered middle zone uses `floor((gap - middle) / 2)`, matching third-party statuses placed in the middle. During responsive two-row reflow, custom templates with a top-level `$fill` keep the right-bearing row right-aligned; a middle zone sharing that row retains its order, not centering.
173
+
174
+ In `compactFormat`, the first top-level `$fill` splits left/right zones and flushes the current chunk. Additional fills are ignored (no compact center zone). Nested fills are nonstructural in both templates. Compact fills were previously ignored; existing templates containing them now opt into alignment. Templates without fill and shipped defaults are unchanged.
175
+
176
+ Compact packing reserves right-hand chunks first, in template order, within `compactMaxLines`, then fits left-hand chunks into the remaining row budgets. Nonempty zones have at least one space between them. `$wrap_sep` separators appear only between chunks on the same row. Right content ends at the inner right edge (one cell inside the terminal margin); an empty right zone retains ordinary left packing. Long chunks truncate and capped omissions use `…`; arbitrarily narrow layouts cannot retain every value.
149
177
 
150
178
  ## Conditional groups
151
179
 
@@ -163,7 +191,7 @@ If every variable inside a group is empty, the group and its literal text are dr
163
191
  - `$session_name` is independent of `components.footer.styles.starship.segments.sessionName` in custom formats.
164
192
  - Built-in wide layout appends cache totals to Tokens, `(sub)` to Cost, and `(auto)` to Context when available.
165
193
  - Custom formats keep `$tokens`, `$cost`, and `$context` backward-compatible. Add `$cache_read`, `$cache_write`, `$subscription`, and `$auto_compaction` explicitly for atomic telemetry.
166
- - `DEFAULT_COMPACT_FOOTER_FORMAT` omits model/provider and atomic telemetry. Add variables to `components.footer.styles.starship.compactFormat` to opt in at narrow widths. The flat `compactFooterFormat` key remains migration-only input.
194
+ - `DEFAULT_COMPACT_FOOTER_FORMAT` omits model/provider, thinking level, and atomic telemetry. Add variables to `components.footer.styles.starship.compactFormat` to opt in at narrow widths. The flat `compactFooterFormat` key remains migration-only input.
167
195
  - Auto-compaction settings refresh at the next normal Footer synchronization. Unsupported Pi capabilities or read errors omit optional markers.
168
196
  - Unknown variables render empty.
169
197
  - `$fill`, `$wrap`, and `$wrap_sep` are structural and never render visible text.
@@ -272,7 +272,23 @@ export type ThinkingStepsComponentConfig = {
272
272
  mode: ThinkingStepsMode;
273
273
  };
274
274
 
275
+ export type ExtensionStatusVisibility = "show" | "hide";
276
+ export type ExtensionStatusVisibilityConfig = {
277
+ defaultVisibility: ExtensionStatusVisibility;
278
+ visibility: Record<string, ExtensionStatusVisibility>;
279
+ };
280
+
281
+ export type HiddenExtensionStatusPlacement = "left" | "middle" | "right";
282
+ export type ExtensionStatusComponentConfig = ExtensionStatusVisibilityConfig & {
283
+ hidden?: {
284
+ defaultPlacement?: HiddenExtensionStatusPlacement;
285
+ placements?: Record<string, HiddenExtensionStatusPlacement>;
286
+ colorModes?: Record<string, ExtensionStatusColorMode>;
287
+ };
288
+ };
289
+
275
290
  export type ComponentsConfig = {
291
+ extensionStatuses: ExtensionStatusComponentConfig;
276
292
  editor: EditorComponentConfig;
277
293
  userMessages: UserMessagesComponentConfig;
278
294
  thinkingSteps: ThinkingStepsComponentConfig;
@@ -404,6 +420,7 @@ export const FOOTER_FORMAT_VARIABLES = [
404
420
  "runtime",
405
421
  "model",
406
422
  "provider",
423
+ "thinkingLevel",
407
424
  "session_duration",
408
425
  "username",
409
426
  "os",
@@ -500,6 +517,7 @@ const defaultStarshipStyle: StarshipFooterStyleConfig = {
500
517
  };
501
518
 
502
519
  const defaultComponents: ComponentsConfig = {
520
+ extensionStatuses: { defaultVisibility: "show", visibility: {} },
503
521
  editor: {
504
522
  codexQuota: false,
505
523
  enabled: true,
@@ -1208,6 +1226,75 @@ function resolveWorkingLineMessages(messages: ConfigRecord): WorkingLineMessages
1208
1226
  return { custom, values: hasOwn(messages, "values") ? legacyValues : preset() };
1209
1227
  }
1210
1228
 
1229
+ export function isHiddenExtensionStatusPlacement(
1230
+ value: unknown,
1231
+ ): value is HiddenExtensionStatusPlacement {
1232
+ return value === "left" || value === "middle" || value === "right";
1233
+ }
1234
+
1235
+ export function getHiddenExtensionStatusPlacement(
1236
+ config: ExtensionStatusComponentConfig,
1237
+ key: string,
1238
+ ): HiddenExtensionStatusPlacement {
1239
+ const placements = config.hidden?.placements;
1240
+ return (
1241
+ (placements && Object.hasOwn(placements, key) ? placements[key] : undefined) ??
1242
+ config.hidden?.defaultPlacement ??
1243
+ "left"
1244
+ );
1245
+ }
1246
+
1247
+ export function getHiddenExtensionStatusColorMode(
1248
+ config: ExtensionStatusComponentConfig,
1249
+ key: string,
1250
+ ): ExtensionStatusColorMode {
1251
+ const modes = config.hidden?.colorModes;
1252
+ return modes && Object.hasOwn(modes, key) && modes[key] === "zentui" ? "zentui" : "original";
1253
+ }
1254
+
1255
+ function resolveExtensionStatusComponent(raw: unknown): ExtensionStatusComponentConfig {
1256
+ const owner = recordValue(raw);
1257
+ const hidden = recordValue(owner.hidden);
1258
+ return {
1259
+ ...(isRecord(owner.hidden)
1260
+ ? {
1261
+ hidden: {
1262
+ ...(isHiddenExtensionStatusPlacement(hidden.defaultPlacement)
1263
+ ? { defaultPlacement: hidden.defaultPlacement }
1264
+ : {}),
1265
+ ...(isRecord(hidden.colorModes)
1266
+ ? {
1267
+ colorModes: Object.fromEntries(
1268
+ Object.entries(hidden.colorModes).filter(
1269
+ (entry): entry is [string, ExtensionStatusColorMode] =>
1270
+ isExtensionStatusColorMode(entry[1]),
1271
+ ),
1272
+ ),
1273
+ }
1274
+ : {}),
1275
+ ...(isRecord(hidden.placements)
1276
+ ? {
1277
+ placements: Object.fromEntries(
1278
+ Object.entries(hidden.placements).filter(
1279
+ (entry): entry is [string, HiddenExtensionStatusPlacement] =>
1280
+ isHiddenExtensionStatusPlacement(entry[1]),
1281
+ ),
1282
+ ),
1283
+ }
1284
+ : {}),
1285
+ },
1286
+ }
1287
+ : {}),
1288
+ defaultVisibility: owner.defaultVisibility === "hide" ? "hide" : "show",
1289
+ visibility: Object.fromEntries(
1290
+ Object.entries(recordValue(owner.visibility)).filter(
1291
+ (entry): entry is [string, ExtensionStatusVisibility] =>
1292
+ entry[1] === "show" || entry[1] === "hide",
1293
+ ),
1294
+ ),
1295
+ };
1296
+ }
1297
+
1211
1298
  function resolveComponents(config: ConfigRecord): ComponentsConfig {
1212
1299
  const components = recordValue(config.components);
1213
1300
  const editor = recordValue(components.editor);
@@ -1254,6 +1341,7 @@ function resolveComponents(config: ConfigRecord): ComponentsConfig {
1254
1341
  const userMessagesSelection = resolveUserMessagesSelection(userMessages, framed, features);
1255
1342
 
1256
1343
  return {
1344
+ extensionStatuses: resolveExtensionStatusComponent(components.extensionStatuses),
1257
1345
  editor: {
1258
1346
  codexQuota: parseBoolean(editor.codexQuota, false),
1259
1347
  ...(isRecord(editor.colors)
@@ -2261,25 +2349,210 @@ export function saveExtensionStatusColorMode(
2261
2349
  key: string,
2262
2350
  colorMode: ExtensionStatusColorMode,
2263
2351
  path = configPath,
2352
+ ): PolishedTuiConfig {
2353
+ return mutateConfig(path, (record) => {
2354
+ const footer = recordValue(recordValue(record.components).footer);
2355
+ const starship = recordValue(recordValue(footer.styles).starship);
2356
+ const statuses = recordValue(starship.extensionStatuses);
2357
+ const colorModes = Object.hasOwn(statuses, "colorModes")
2358
+ ? recordValue(statuses.colorModes)
2359
+ : recordValue(recordValue(record.extensionStatuses).colorModes);
2360
+ record.components = overlayKnown(record.components, {
2361
+ footer: {
2362
+ styles: {
2363
+ starship: { extensionStatuses: { colorModes: { ...colorModes, [key]: colorMode } } },
2364
+ },
2365
+ },
2366
+ });
2367
+ });
2368
+ }
2369
+
2370
+ /** Hidden placement edits remain sparse and never snapshot another owner's preferences. */
2371
+ export function saveHiddenExtensionStatusDefaultPlacement(
2372
+ placement: HiddenExtensionStatusPlacement,
2373
+ path = configPath,
2374
+ ): PolishedTuiConfig {
2375
+ return mutateConfig(path, (record) => {
2376
+ record.components = overlayKnown(record.components, {
2377
+ extensionStatuses: { hidden: { defaultPlacement: placement } },
2378
+ });
2379
+ });
2380
+ }
2381
+
2382
+ export function saveHiddenExtensionStatusPlacement(
2383
+ key: string,
2384
+ placement: HiddenExtensionStatusPlacement | undefined,
2385
+ path = configPath,
2386
+ ): PolishedTuiConfig {
2387
+ return mutateConfig(path, (record) => {
2388
+ if (placement === undefined) {
2389
+ const owner = recordValue(recordValue(record.components).extensionStatuses);
2390
+ delete recordValue(recordValue(owner.hidden).placements)[key];
2391
+ } else {
2392
+ record.components = overlayKnown(record.components, {
2393
+ extensionStatuses: { hidden: { placements: { [key]: placement } } },
2394
+ });
2395
+ }
2396
+ });
2397
+ }
2398
+
2399
+ /** Original is Hidden's default; choosing it removes only this color override. */
2400
+ export function saveHiddenExtensionStatusColorMode(
2401
+ key: string,
2402
+ colorMode: ExtensionStatusColorMode,
2403
+ path = configPath,
2404
+ ): PolishedTuiConfig {
2405
+ return mutateConfig(path, (record) => {
2406
+ if (colorMode === "original") {
2407
+ const owner = recordValue(recordValue(record.components).extensionStatuses);
2408
+ delete recordValue(recordValue(owner.hidden).colorModes)[key];
2409
+ } else {
2410
+ record.components = overlayKnown(record.components, {
2411
+ extensionStatuses: { hidden: { colorModes: { [key]: colorMode } } },
2412
+ });
2413
+ }
2414
+ });
2415
+ }
2416
+
2417
+ export function saveExtensionStatusDefaultVisibility(
2418
+ visibility: ExtensionStatusVisibility,
2419
+ path = configPath,
2264
2420
  ): PolishedTuiConfig {
2265
2421
  return saveComponentsMutation(
2266
- ["footer"],
2422
+ ["extensionStatuses"],
2267
2423
  (components) => {
2268
- Object.defineProperty(components.footer.styles.starship.extensionStatuses.colorModes, key, {
2269
- value: colorMode,
2270
- enumerable: true,
2271
- configurable: true,
2272
- writable: true,
2273
- });
2424
+ components.extensionStatuses.defaultVisibility = visibility;
2425
+ },
2426
+ path,
2427
+ );
2428
+ }
2429
+
2430
+ /** Undefined resumes the independent default, without changing Starship presentation. */
2431
+ export function saveExtensionStatusVisibility(
2432
+ key: string,
2433
+ visibility: ExtensionStatusVisibility | undefined,
2434
+ path = configPath,
2435
+ ): PolishedTuiConfig {
2436
+ return saveComponentsMutation(
2437
+ ["extensionStatuses"],
2438
+ (components) => {
2439
+ if (visibility === undefined) delete components.extensionStatuses.visibility[key];
2440
+ else
2441
+ Object.defineProperty(components.extensionStatuses.visibility, key, {
2442
+ value: visibility,
2443
+ enumerable: true,
2444
+ configurable: true,
2445
+ writable: true,
2446
+ });
2274
2447
  },
2275
2448
  path,
2449
+ (record) => {
2450
+ // overlayKnown preserves unknown leaves; explicitly remove a reset override.
2451
+ if (visibility === undefined) {
2452
+ const owner = recordValue(recordValue(record.components).extensionStatuses);
2453
+ delete recordValue(owner.visibility)[key];
2454
+ }
2455
+ },
2276
2456
  );
2277
2457
  }
2278
2458
 
2459
+ export type ExtensionStatusChoice = "default" | ExtensionStatusPlacement;
2460
+
2461
+ /** Save one menu choice in a single atomic write, without snapshotting other components. */
2462
+ export function saveExtensionStatusDefaultChoice(
2463
+ placement: ExtensionStatusPlacement,
2464
+ style: FooterStyle,
2465
+ path = configPath,
2466
+ ): PolishedTuiConfig {
2467
+ return mutateConfig(path, (record) => {
2468
+ record.components = overlayKnown(record.components, {
2469
+ extensionStatuses: { defaultVisibility: placement === "off" ? "hide" : "show" },
2470
+ });
2471
+ if (placement === "off") return;
2472
+ if (style === "hidden") {
2473
+ record.components = overlayKnown(record.components, {
2474
+ extensionStatuses: { hidden: { defaultPlacement: placement } },
2475
+ });
2476
+ } else {
2477
+ // Native retains Starship positioning as a dormant preference; Pi owns its layout.
2478
+ record.components = overlayKnown(record.components, {
2479
+ footer: { styles: { starship: { extensionStatuses: { defaultPlacement: placement } } } },
2480
+ });
2481
+ }
2482
+ });
2483
+ }
2484
+
2485
+ /** Default removes both visibility and current-mode placement, shadowing legacy Starship maps. */
2486
+ export function saveExtensionStatusChoice(
2487
+ key: string,
2488
+ choice: ExtensionStatusChoice,
2489
+ style: FooterStyle,
2490
+ path = configPath,
2491
+ ): PolishedTuiConfig {
2492
+ return mutateConfig(path, (record) => {
2493
+ const components = recordValue(record.components);
2494
+ const owner = recordValue(components.extensionStatuses);
2495
+ const hidden = recordValue(owner.hidden);
2496
+ const starship = recordValue(recordValue(recordValue(components.footer).styles).starship);
2497
+ const canonicalStatuses = recordValue(starship.extensionStatuses);
2498
+ if (choice === "default") {
2499
+ delete recordValue(owner.visibility)[key];
2500
+ if (style === "hidden") {
2501
+ delete recordValue(hidden.placements)[key];
2502
+ } else {
2503
+ // An empty canonical map must remain to prevent top-level legacy fallback.
2504
+ const placements = {
2505
+ ...recordValue(
2506
+ Object.hasOwn(canonicalStatuses, "placements")
2507
+ ? canonicalStatuses.placements
2508
+ : recordValue(record.extensionStatuses).placements,
2509
+ ),
2510
+ };
2511
+ delete placements[key];
2512
+ record.components = overlayKnown(record.components, {
2513
+ footer: { styles: { starship: { extensionStatuses: { placements: {} } } } },
2514
+ });
2515
+ const footer = recordValue(recordValue(record.components).footer);
2516
+ const savedStarship = recordValue(recordValue(footer.styles).starship);
2517
+ recordValue(savedStarship.extensionStatuses).placements = placements;
2518
+ }
2519
+ return;
2520
+ }
2521
+ record.components = overlayKnown(record.components, {
2522
+ extensionStatuses: { visibility: { [key]: choice === "off" ? "hide" : "show" } },
2523
+ });
2524
+ if (choice === "off") return;
2525
+ if (style === "hidden") {
2526
+ record.components = overlayKnown(record.components, {
2527
+ extensionStatuses: { hidden: { placements: { [key]: choice } } },
2528
+ });
2529
+ } else {
2530
+ const placements = Object.hasOwn(canonicalStatuses, "placements")
2531
+ ? recordValue(canonicalStatuses.placements)
2532
+ : recordValue(recordValue(record.extensionStatuses).placements);
2533
+ record.components = overlayKnown(record.components, {
2534
+ footer: {
2535
+ styles: {
2536
+ starship: { extensionStatuses: { placements: { ...placements, [key]: choice } } },
2537
+ },
2538
+ },
2539
+ });
2540
+ }
2541
+ });
2542
+ }
2543
+
2279
2544
  /** Explicit all-owner migration; callers must obtain confirmation before invoking. */
2280
2545
  export function migrateComponentSelections(path = configPath): PolishedTuiConfig {
2281
2546
  return saveComponentsMutation(
2282
- ["editor", "userMessages", "thinkingSteps", "workingLine", "selectorBorders", "footer"],
2547
+ [
2548
+ "editor",
2549
+ "userMessages",
2550
+ "thinkingSteps",
2551
+ "workingLine",
2552
+ "selectorBorders",
2553
+ "footer",
2554
+ "extensionStatuses",
2555
+ ],
2283
2556
  () => {},
2284
2557
  path,
2285
2558
  );