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 +7 -7
- package/docs/configuration.md +54 -11
- package/docs/footer-format.md +31 -3
- package/extensions/zentui/config.ts +281 -8
- package/extensions/zentui/extension-status-controller.ts +147 -0
- package/extensions/zentui/extension-status-settings.ts +40 -0
- package/extensions/zentui/extension-status.ts +109 -1
- package/extensions/zentui/footer-format.ts +16 -0
- package/extensions/zentui/footer-layout.ts +80 -3
- package/extensions/zentui/footer.ts +93 -47
- package/extensions/zentui/index.ts +34 -10
- package/extensions/zentui/settings-command.ts +86 -55
- package/extensions/zentui/working-line.ts +60 -17
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<h1 align="center">Zentui</h1>
|
|
2
2
|
|
|
3
|
-
<p align="center">A
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
package/docs/configuration.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
package/docs/footer-format.md
CHANGED
|
@@ -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
|
|
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
|
-
["
|
|
2422
|
+
["extensionStatuses"],
|
|
2267
2423
|
(components) => {
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
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
|
-
[
|
|
2547
|
+
[
|
|
2548
|
+
"editor",
|
|
2549
|
+
"userMessages",
|
|
2550
|
+
"thinkingSteps",
|
|
2551
|
+
"workingLine",
|
|
2552
|
+
"selectorBorders",
|
|
2553
|
+
"footer",
|
|
2554
|
+
"extensionStatuses",
|
|
2555
|
+
],
|
|
2283
2556
|
() => {},
|
|
2284
2557
|
path,
|
|
2285
2558
|
);
|