pi-zentui 0.16.0 → 0.17.1

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
@@ -8,10 +8,13 @@ A Starship-inspired statusline and Opencode-style TUI for [Pi](https://pi.dev).
8
8
 
9
9
  ## What is this?
10
10
 
11
- Zentui brings two popular aesthetics to Pi:
11
+ Zentui styles three major Pi surfaces independently:
12
12
 
13
- - **[Starship](https://starship.rs/) footer** — shows your current directory, git branch, git status indicators, and runtime/version detection in a compact, icon-rich format
14
- - **[Opencode](https://github.com/opencode-ai/opencode) editor** — clean bordered input box with accent rail, copy-friendly mode, and model/provider display inside the editor frame
13
+ - **Editor** — selectable opencode, low-rail opencode, and minimalist input frames inspired by [Opencode](https://github.com/opencode-ai/opencode)
14
+ - **User messages** — selectable framed, framed copy-friendly, compact, and labeled transcript messages
15
+ - **[Starship](https://starship.rs/) footer** — current directory, Git, runtime, context, tokens, cost, and other configurable segments
16
+
17
+ Editor, User messages, and selector borders use an `enabled` field. Footer uses one `style`: `native`, `starship`, or `hidden`. **Appearance** (selector borders and icons) and **Layout** (the fixed-editor experiment) remain supporting configuration domains.
15
18
 
16
19
  ## Features
17
20
 
@@ -24,18 +27,55 @@ Zentui brings two popular aesthetics to Pi:
24
27
  - Optional segments (off by default): selected model/provider, `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
28
  - Right side shows context usage, token counts, and cost
26
29
  - Built-in footer segments can be shown or hidden individually from `/zentui`
27
- - Fully custom Starship-style layout via a `footerFormat` template string — see [Footer Format Template](#footer-format-template)
30
+ - Fully custom Starship-style layout via the `components.footer.styles.starship.format` template string — see [Footer Format Template](#footer-format-template)
28
31
  - Third-party Pi extension statuses from `ctx.ui.setStatus()` can be shown on the left,
29
32
  middle, or right side, or hidden per status key from `/zentui`
30
33
 
31
34
  ### Editor (Opencode-inspired)
32
35
 
33
- - Bordered input box with configurable accent rail and border colors
34
- - Model name and provider displayed inside the editor frame
35
- - Configurable model, provider, and thinking-level indicator colors
36
- - Prompt-box-style user messages matching the ZentUI input chrome
37
- - Copy-friendly mode hides editor and previous-message rail glyphs so terminal selection copies less chrome
38
- - **Fixed editor** (experimental, opt-in): Pin the editor and footer at the bottom of the terminal while the transcript scrolls above
36
+ - `opencode` (default) keeps an accent rail on every interior row
37
+ - `opencode-copy-friendly` (**Opencode (copy-friendly)** in `/zentui`) preserves the low-rail rendering for clean terminal selection
38
+ - `minimalist` moves session name, cost, model, thinking, context, Git, configurable path, Bash state, and turn duration into a rounded frame
39
+ - The selected model label and provider appear inside both Opencode editor variants; the model ID is used by default, while `components.editor.modelLabel: "name"` uses the display name with ID fallback.
40
+ - Opencode autocomplete rows retain Pi's original unframed trailing layout; Minimalist keeps autocomplete inside its rounded frame
41
+ - Configurable model, provider, thinking-level, accent, and border colors
42
+ - **Fixed editor** (experimental, opt-in): pin the editor cluster at the bottom of the terminal while the transcript scrolls above, independently of selected editor and Footer modes
43
+
44
+ Editor previews:
45
+
46
+ ```text
47
+ opencode opencode-copy-friendly minimalist
48
+ ──────────────────── ──────────────────── ╭─ session ── model ╮
49
+
50
+ │ prompt › prompt │ prompt │
51
+
52
+ │ metadata metadata ╰─ git ───── path ──╯
53
+ ──────────────────── ────────────────────
54
+ ```
55
+
56
+ ### User messages
57
+
58
+ - `framed` (default) preserves the full-width bordered prompt box with an accent rail
59
+ - `framed-copy-friendly` (**Framed (copy-friendly)** in `/zentui`) keeps the full-width horizontal borders and blank spacer rows, removes the copied accent rail, and retains a one-cell leading gutter before body text.
60
+ - `compact` uses only an accent rail, with no border or padding rows
61
+ - `labeled` uses a rounded box with the fixed label `User`
62
+ - Disabling User-message styling delegates byte-for-byte to Pi's native renderer; native is not a style ID
63
+ - No custom `plain` message style is provided
64
+
65
+ ```text
66
+ framed framed-copy-friendly
67
+ ──────────────────── ────────────────────
68
+
69
+ │ Message Message
70
+
71
+ ──────────────────── ────────────────────
72
+
73
+ compact labeled
74
+ │ Message ╭─ User ───────────╮
75
+ │ Continued │ Message │
76
+ │ Continued │
77
+ ╰──────────────────╯
78
+ ```
39
79
 
40
80
  ### Git Status Icons
41
81
 
@@ -130,16 +170,20 @@ pi install git:github.com/lmilojevicc/pi-zentui
130
170
 
131
171
  User config lives at `~/.pi/agent/zentui.json`. The file is optional: missing or invalid known values fall back to Zentui defaults, unknown keys are ignored at runtime, and `/zentui` can patch color-source settings, UI feature toggles, built-in footer segment visibility, and active third-party status placements.
132
172
 
133
- The interactive `/zentui` menu is split into exactly six sections, in this order. Use `Tab` and `Shift+Tab` to switch sections. Every listed control patches the shown JSON equivalent:
173
+ The interactive `/zentui` menu is split into exactly eight component-oriented sections, in this order. Use `Tab` and `Shift+Tab` to switch sections:
174
+
175
+ 1. **Appearance** — selector-border enablement, style, and colors; icon mode.
176
+ 2. **Editor** — editor enablement, style, colors, model label, border behavior, viewport indicators, and settings for the selected editor style.
177
+ 3. **User messages** — message enablement, `framed | framed-copy-friendly | compact | labeled` style selection (including **Framed (copy-friendly)**), and colors.
178
+ 4. **Layout** — fixed-editor enablement, mouse scrolling, and copy notices.
179
+ 5. **Footer** — `Native | Starship | Hidden` style selection. Starship additionally shows colors, model label, responsive layout, separator, context style, and path display.
180
+ 6. **Segments** — visibility toggles for non-Git Starship segments.
181
+ 7. **Git** — Starship Git segment and probe controls.
182
+ 8. **Extensions** — Starship extension-status placement and color controls for active keys.
134
183
 
135
- 1. **Appearance** Starship/footer colors (`colorSources.starship`); editor + previous-message colors (`colorSources.editor` and `colorSources.userMessages`); separator (`separator`); icon mode (`icons.mode`).
136
- 2. **Editor** — editor enabled (`features.editor`); editor border color mode (`editorBorderColorMode`); editor model label (`editorModelLabel`); copy-friendly mode (`features.copyFriendly`); viewport indicators (`features.viewportIndicators`); fixed editor (`fixedEditor.enabled`); and, while fixed editor is enabled, mouse scroll (`fixedEditor.mouseScroll`) and copy notice (`fixedEditor.copyNotice`).
137
- 3. **Footer** — status line enabled (`features.statusLine`); responsive footer (`responsiveFooter`); compact footer rows (`compactFooterMaxLines`); context style (`contextStyle`); path display (`pathDisplay.mode`); path depth (`pathDisplay.depth`).
138
- 4. **Segments** — visibility toggles for every non-Git built-in segment under `footerSegments`: `cwd`, `sessionName`, `runtime`, `modelInfo`, `context`, `tokens`, `cost`, `sessionDuration`, `username`, `time`, `os`, and `packageVersion`.
139
- 5. **Git** — Git branch visibility (`footerSegments.gitBranch`); branch length (`gitBranch.maxLength`); Git status visibility (`footerSegments.gitStatus`); Git counts visibility (`footerSegments.gitCounts`); Git commit visibility (`footerSegments.gitCommit`); commit-only-detached (`gitCommit.onlyDetached`); exact-match tag (`gitCommit.showTag`); Git metrics visibility (`footerSegments.gitMetrics`); hide zero metrics (`gitMetrics.onlyNonzero`); ignore submodules (`gitMetrics.ignoreSubmodules`).
140
- 6. **Extensions** — default placement (`extensionStatuses.defaultPlacement`) first, followed by placement (`extensionStatuses.placements[key]`) and color (`extensionStatuses.colorModes[key]`) controls for currently active status keys.
184
+ Editor and User messages retain independent enablement and style configuration. Footer's single style selects Pi's built-in Footer (`Native`), Zentui's Starship Footer, or an owned zero-row Footer (`Hidden`). Color and model-label rows update only their owning component.
141
185
 
142
- Free-form values such as custom formats, raw colors/styles, numeric values outside the shown presets, and inactive extension keys remain JSON-only.
186
+ Starship-specific Footer rows are shown only while Starship is selected. The **Segments**, **Git**, and **Extensions** sections remain available for preconfiguration under every Footer style. Free-form values such as custom formats, Opencode metadata format, raw colors/styles, numeric values outside the shown presets, and inactive extension keys remain JSON-only.
143
187
 
144
188
  Useful slash-command shortcuts:
145
189
 
@@ -149,10 +193,13 @@ Useful slash-command shortcuts:
149
193
  /zentui statusline enable
150
194
  /zentui statusline disable
151
195
  /zentui editor toggle
196
+ /zentui messages enable
197
+ /zentui messages disable
198
+ /zentui messages toggle
152
199
  /zentui statusline toggle
153
- /zentui copy-friendly enable
154
- /zentui copy-friendly disable
155
- /zentui copy-friendly toggle
200
+ /zentui messages
201
+ /zentui user-messages
202
+ /zentui layout
156
203
  /zentui viewport-indicators enable
157
204
  /zentui viewport-indicators disable
158
205
  /zentui viewport-indicators toggle
@@ -163,30 +210,125 @@ Useful slash-command shortcuts:
163
210
  /zentui format clear
164
211
  ```
165
212
 
213
+ `footer`, `statusline`, `status`, and `status line` are aliases: enable selects Starship, disable selects Native, and toggle selects Native only from Starship (Native or Hidden toggle to Starship).
214
+
166
215
  Default config values — copy this and change any value you want:
167
216
 
168
217
  ```json
169
218
  {
170
219
  "projectRefreshIntervalMs": 30000,
171
- "footerFormat": "",
172
- "responsiveFooter": true,
173
- "compactFooterFormat": "$cwd$wrap(in $session_name)$wrap(on $git_branch) $git_status$wrap$context$wrap_sep$tokens",
174
- "compactFooterMaxLines": 2,
175
- "editorMetadataFormat": "$model $provider( $thinking)",
176
- "separator": "pipe",
177
- "contextStyle": "text",
178
- "editorModelLabel": "id",
179
- "editorBorderColorMode": "static",
180
- "contextThresholds": {
181
- "warning": 70,
182
- "error": 90
220
+ "components": {
221
+ "editor": {
222
+ "enabled": true,
223
+ "style": "opencode",
224
+ "colorSource": "theme",
225
+ "borderColorMode": "static",
226
+ "modelLabel": "id",
227
+ "viewportIndicators": true,
228
+ "styles": {
229
+ "opencode": {
230
+ "metadataFormat": "$model $provider( $thinking)"
231
+ },
232
+ "opencode-copy-friendly": {
233
+ "metadataFormat": "$model $provider( $thinking)"
234
+ },
235
+ "minimalist": {
236
+ "pathDisplay": "compact",
237
+ "contextFormat": "percent",
238
+ "contextGauge": false,
239
+ "showSessionName": true,
240
+ "showTimer": true,
241
+ "showCost": true,
242
+ "showGit": true,
243
+ "contextThresholds": {
244
+ "warning": 70,
245
+ "error": 90
246
+ }
247
+ }
248
+ }
249
+ },
250
+ "userMessages": {
251
+ "enabled": true,
252
+ "style": "framed",
253
+ "colorSource": "theme",
254
+ "styles": {
255
+ "framed": {},
256
+ "framed-copy-friendly": {},
257
+ "compact": {},
258
+ "labeled": {}
259
+ }
260
+ },
261
+ "selectorBorders": {
262
+ "enabled": true,
263
+ "style": "zentui",
264
+ "colorSource": "theme"
265
+ },
266
+ "footer": {
267
+ "style": "starship",
268
+ "colorSource": "theme",
269
+ "modelLabel": "id",
270
+ "styles": {
271
+ "starship": {
272
+ "format": "",
273
+ "responsive": true,
274
+ "compactFormat": "$cwd$wrap(in $session_name)$wrap(on $git_branch) $git_status$wrap$context$wrap_sep$tokens",
275
+ "compactMaxLines": 2,
276
+ "separator": "pipe",
277
+ "contextStyle": "text",
278
+ "contextThresholds": {
279
+ "warning": 70,
280
+ "error": 90
281
+ },
282
+ "pathDisplay": {
283
+ "mode": "basename",
284
+ "depth": 0
285
+ },
286
+ "segments": {
287
+ "cwd": true,
288
+ "sessionName": true,
289
+ "gitBranch": true,
290
+ "gitStatus": true,
291
+ "gitCounts": false,
292
+ "runtime": true,
293
+ "modelInfo": false,
294
+ "context": true,
295
+ "tokens": true,
296
+ "cost": true,
297
+ "sessionDuration": false,
298
+ "username": false,
299
+ "time": false,
300
+ "os": false,
301
+ "packageVersion": false,
302
+ "gitCommit": false,
303
+ "gitMetrics": false
304
+ },
305
+ "gitBranch": {
306
+ "maxLength": "full"
307
+ },
308
+ "gitCommit": {
309
+ "hashLength": 7,
310
+ "onlyDetached": true,
311
+ "showTag": true
312
+ },
313
+ "gitMetrics": {
314
+ "onlyNonzero": true,
315
+ "ignoreSubmodules": false
316
+ },
317
+ "extensionStatuses": {
318
+ "defaultPlacement": "right",
319
+ "placements": {},
320
+ "colorModes": {}
321
+ }
322
+ }
323
+ }
324
+ }
183
325
  },
184
- "pathDisplay": {
185
- "mode": "basename",
186
- "depth": 0
187
- },
188
- "gitBranch": {
189
- "maxLength": "full"
326
+ "layout": {
327
+ "fixedEditor": {
328
+ "enabled": false,
329
+ "mouseScroll": true,
330
+ "copyNotice": true
331
+ }
190
332
  },
191
333
  "icons": {
192
334
  "mode": "auto",
@@ -242,103 +384,67 @@ Default config values — copy this and change any value you want:
242
384
  "editorThinkingMedium": "thinkingMedium",
243
385
  "editorThinkingHigh": "thinkingHigh",
244
386
  "editorThinkingXhigh": "thinkingXhigh"
245
- },
246
- "colorSources": {
247
- "starship": "theme",
248
- "editor": "theme",
249
- "userMessages": "theme"
250
- },
251
- "features": {
252
- "editor": true,
253
- "statusLine": true,
254
- "copyFriendly": false,
255
- "viewportIndicators": true
256
- },
257
- "footerSegments": {
258
- "cwd": true,
259
- "sessionName": true,
260
- "gitBranch": true,
261
- "gitStatus": true,
262
- "gitCounts": false,
263
- "runtime": true,
264
- "modelInfo": false,
265
- "context": true,
266
- "tokens": true,
267
- "cost": true,
268
- "sessionDuration": false,
269
- "username": false,
270
- "time": false,
271
- "os": false,
272
- "packageVersion": false,
273
- "gitCommit": false,
274
- "gitMetrics": false
275
- },
276
- "gitCommit": {
277
- "hashLength": 7,
278
- "onlyDetached": true,
279
- "showTag": true
280
- },
281
- "gitMetrics": {
282
- "onlyNonzero": true,
283
- "ignoreSubmodules": false
284
- },
285
- "extensionStatuses": {
286
- "defaultPlacement": "right",
287
- "placements": {},
288
- "colorModes": {}
289
- },
290
- "fixedEditor": {
291
- "enabled": false,
292
- "mouseScroll": true,
293
- "copyNotice": true
294
387
  }
295
388
  }
296
389
  ```
297
390
 
298
391
  - 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`.
299
392
  - `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`.
300
- - `contextStyle`: `text` (default), `gauge`, or `text+gauge` for the context segment. Finite context percentages use one decimal place. Context usage refreshes during assistant streaming; token and cost totals remain canonical and finalize at turn boundaries.
301
- - `editorModelLabel`: controls the model shown in the editor frame, built-in model-info footer segment, and footer `$model` variable. `id` (default) shows the model id; `name` shows the model's display name (including custom `name` values set in `models.json`), falling back to the id when no name is set.
302
- - `editorBorderColorMode`: `static` (default) uses `colors.editorBorder`; `adaptive` follows Pi's current shell-mode and thinking-level editor border color. Cycle it from the `/zentui` **Editor** tab.
303
- - `editorMetadataFormat`: JSON-only template for the left side of the editor metadata row. Missing, non-string, or empty values restore the default `$model $provider( $thinking)` layout; non-empty strings, including whitespace-only strings, are preserved. See [Editor Metadata Format](#editor-metadata-format) below.
304
- - `separator`: controls the default footer layout and extension-status connectors: `pipe` (default, ` | `), `dot` (` · `), `chevron` (` › `), or `none` (one space). Cycle it from the `/zentui` **Appearance** tab. This selects the separator glyph; `colors.separator` controls its color. Custom `footerFormat` literals and `$sep` keep their existing behavior.
305
- - `contextThresholds`: `{ warning, error }` percentages (default `70` / `90`) that select contextNormal / contextWarning / contextError colors.
306
- - `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` **Footer** tab cycles path mode and path depth (`0`–`5`; depth is ignored for basename). Example: `~/Projects/foo/bar` with `depth: 2` → `…/foo/bar`.
307
- - `gitBranch.maxLength`: visible width of the built-in branch name and `$git_branch` / `$branch`. The default `full` preserves the complete name; any positive integer uses that width including the trailing `…`. `/zentui` **Git** cycles `full`, `10`, `20`, `30`, `40`, and `50`; custom positive integers can be set in JSON.
308
- - `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.
309
- - `colorSources`: `theme` maps styles through Pi theme tokens; `terminal` emits terminal colors. `/zentui` switches these sources; manual JSON controls specific style values.
310
- - `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. `viewportIndicators` preserves Pi's native `↑ N more` / `↓ N more` wrapped-row counts in Zentui's editor borders (default `true`). All four can be changed from `/zentui` or direct slash-command arguments.
311
- - `footerSegments`: show or hide individual built-in footer segments (`cwd`, `sessionName`, `gitBranch`, `gitStatus`, `gitCounts`, `gitCommit`, `gitMetrics`, `runtime`, `modelInfo`, `packageVersion`, `sessionDuration`, `username`, `time`, `os`, `context`, `tokens`, `cost`). `modelInfo` is off by default and shows the selected model plus the provider unless the model label already contains it. Toggle non-Git segments from **Segments** and Git segments from **Git** in `/zentui`.
312
- - `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` **Footer** tab configures responsive behavior, compact rows, context style, and path display mode/depth; **Appearance** configures separator and icon mode; **Git** configures branch length; set or clear custom formats with `/zentui format`.
313
- - `responsiveFooter`: enabled by default. Zentui keeps the current aligned one-row footer while every settings-resolved left/middle/right zone fits without layout truncation. Otherwise it tries two complete left-aligned rows, preferring `left` / `middle right` and then `left middle` / `right`. Only when neither split fits does it use `compactFooterFormat`. Set `false` to restore the legacy one-row fitting behavior. Selection uses measured terminal-cell width, not fixed device breakpoints.
314
- - `compactFooterFormat`: JSON-only template used by the compact stage. The default keeps cwd, session name, git branch/status, context, and abbreviated token/cache metrics. A top-level `$wrap` is an automatic wrap opportunity: one space on the same row or no space at a row break. `$wrap_sep` is the same kind of boundary but renders the styled ` | ` divider only when its adjacent chunks share a row. Nested uses of either boundary remain empty variables. `$fill` is ignored. A standalone `$extensions` chunk inserts active non-`off` extension statuses in left/middle/right placement order; embedded uses render empty. Custom `footerFormat` values use this built-in compact fallback unless this key is also customized.
315
- - `compactFooterMaxLines`: `1`, `2`, `3`, or `"unlimited"` (default `2`). Finite limits crop remaining chunks with exactly one trailing `…`. Compact cwd always uses basename mode; cwd/session/branch chunks target half the available row width before final ANSI-aware clamping. `/zentui` exposes the responsive toggle and row limit, while compact format editing remains JSON-only. Pi supplies footer width but no supported viewport-height budget, so `"unlimited"` is explicit.
316
- - `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.
317
- - `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.
318
- - `extensionStatuses`: controls third-party statuses published by other Pi extensions through `ctx.ui.setStatus()`. `defaultPlacement` and each `placements` value can be `off`, `left`, `middle`, or `right`. The **Extensions** tab in `/zentui` lists only statuses that are currently active. `defaultPlacement` applies only when a status key has no entry in `placements`; keyed overrides always win.
393
+ - `components.editor`: owns editor enablement, `opencode | opencode-copy-friendly | minimalist` style selection, color source, border mode, model label, viewport indicators, and all three editor-style configurations.
394
+ - `components.userMessages`: owns message enablement, `framed | framed-copy-friendly | compact | labeled` style selection, and color source. `framed-copy-friendly` remains Zentui-rendered; disabling the component delegates to Pi's native renderer.
395
+ - `components.selectorBorders`: owns selector-border enablement, the fixed `zentui` style, and its color source.
396
+ - `components.footer`: owns `native | starship | hidden` style selection, Footer color source, Footer model label, and every Starship option under `styles.starship` (formats, segments, context thresholds, path, Git, and extension statuses). Native restores Pi's built-in Footer; Hidden installs an empty component with zero rows.
397
+ - `layout.fixedEditor`: owns fixed-layout enablement, mouse scrolling, and copy notices. Layout activation depends on Pi compatibility inspection, not on Zentui editor/Footer style.
398
+ - Editor and Footer `modelLabel` values are independent and have separate controls in the **Editor** and **Footer** sections.
399
+ - Selector borders support only `zentui`; set their owning `enabled` field to `false` for native Pi behavior.
400
+ - Flat released keys such as `editorStyle`, `features`, `footerFormat`, and `fixedEditor` remain accepted as migration input. `components.footer.enabled` and `features.statusLine` migrate to Starship or Native when no valid Footer style is present; Hidden projects `features.statusLine: false`. Canonical `components` and `layout` paths are the primary JSON interface, and component saves materialize canonical snapshots.
401
+ - Explicit unsupported future component style IDs are preserved unchanged on disk but fail open at runtime: Editor, User-message, and selector-border customization stay disabled, while Footer behavior is Native. Missing, empty, or malformed style values continue normal default and legacy migration behavior.
402
+ - The flat properties returned by `mergeConfig`, `loadConfig`, and save helpers are deprecated compatibility output and will remain available until at least the next major release. This output deprecation is separate from accepted legacy flat JSON input.
403
+ - `polished` and `polished-copy-friendly` remain read-only migration aliases for `opencode` and `opencode-copy-friendly`. Legacy `features.copyFriendly` and the old nested Editor/message `copyFriendly` fields are read-only migration inputs: message copy-friendly `true` selects `framed-copy-friendly` rather than disabling custom rendering. Explicit Editor or User-message style saves remove only the corresponding obsolete nested flag; raw released feature keys, unknown fields, and unknown style data remain preserved as user-owned migration data.
319
404
  - The shown `editor*` values match the default `theme` source. Omit those keys to keep Zentui's source-aware defaults when switching between `theme` and `terminal`.
320
- - `editorAccent` styles the active editor rail and previous user-message rail when `features.copyFriendly` is disabled.
321
- - `editorPrompt` styles the copy-friendly editor prompt glyph. Omit it to use `editorAccent`, then the default accent fallback.
322
- - `editorBorder` styles previous user-message top/bottom borders and the active editor in static border color mode; the border glyph stays `─`.
405
+ - `editorAccent` styles Editor and User-message accent rails and the labeled message label.
406
+ - `editorPrompt` styles the `opencode-copy-friendly` Editor prompt glyph. Omit it to use `editorAccent`, then the default accent fallback.
407
+ - `editorBorder` styles the `framed` and `framed-copy-friendly` previous-message top/bottom borders and the active editor in static border color mode; the border glyph stays `─`.
323
408
  - `editorModel`, `editorProvider`, and `editorThinking*` style the editor metadata. `editorThinking` applies to every non-`off` thinking level unless a level-specific key is set.
324
409
 
325
- Tip: when using copy-friendly mode, setting Pi's `editorPaddingX` to `1` in `~/.pi/agent/settings.json` keeps a small left gutter without copying a rail glyph.
410
+ Tip: with `opencode-copy-friendly`, setting Pi's `editorPaddingX` to `1` in `~/.pi/agent/settings.json` keeps a small left gutter without copying a rail glyph.
411
+
412
+ ## Minimalist editor style
413
+
414
+ Set `components.editor.style` to `minimalist` or select it from the `/zentui` **Editor** tab. The rounded frame shows viewport counts, Bash state, the current/completed turn duration, and the explicit Pi session name at top left; cost, model, thinking level, and context usage at top right; viewport count plus Git branch/status at bottom left; and the configured path at bottom right. Unnamed sessions add no placeholder. Autocomplete stays inside the frame when Pi's existing editor output can be split safely. Unknown third-party editor layouts fail open without decoration.
415
+
416
+ The Minimalist editor is inspired by [pi-custom-input](https://github.com/VinhLe1410/pi-custom-input), with an independent implementation in Zentui.
417
+
418
+ While `minimalist` is selected, the `/zentui` **Editor** area shows its focused controls without repeating the style name on every row. Path examples are `src` (`compact`), `zentui/src` (`project`), and `~/Projects/zentui/src` (`full`). Context can render as `11%`, `11%/372k`, or—with the gauge enabled and enough room—`[█░░░░] 11%/372k`. The gauge shortens or disappears before the context text at narrow widths. Session name, timer, cost, and Git can be hidden independently; model, thinking, and context remain structurally stable.
419
+
420
+ Footer visibility is controlled by `components.footer.style`: use `starship`, `native`, or `hidden`. Minimalist editor decoration and the Starship Footer may be shown together, including at narrow widths or after decoration fallback. Minimalist style does not remove Pi's header; the experimental fixed-editor layout remains separate.
326
421
 
327
422
  ## Editor Metadata Format
328
423
 
329
- Set `editorMetadataFormat` in `~/.pi/agent/zentui.json` to customize the left side of the editor metadata row:
424
+ Set `metadataFormat` under either opencode style in `~/.pi/agent/zentui.json` to customize that style's metadata row. The two variants retain independent values:
330
425
 
331
426
  ```json
332
427
  {
333
- "editorMetadataFormat": "$model_name ($model_id)( · $provider)( · $thinking)( · $session_name)"
428
+ "components": {
429
+ "editor": {
430
+ "styles": {
431
+ "opencode": {
432
+ "metadataFormat": "$model_name ($model_id)( · $provider)( · $thinking)( · $session_name)"
433
+ },
434
+ "opencode-copy-friendly": {
435
+ "metadataFormat": "$model( · $provider)"
436
+ }
437
+ }
438
+ }
439
+ }
334
440
  }
335
441
  ```
336
442
 
337
- The syntax follows the relevant `footerFormat` conventions: `$variable` and `${variable}` references, literal text and spaces, and conditional groups `( ... )` that disappear when all variables inside are empty. Unknown variables and `$fill` render empty; `$fill` never creates an editor layout zone because the right side remains reserved for structural Vim status.
443
+ The syntax follows the Footer Format Template conventions: `$variable` and `${variable}` references, literal text and spaces, and conditional groups `( ... )` that disappear when all variables inside are empty. Unknown variables and `$fill` render empty; `$fill` never creates an editor layout zone because the right side remains reserved for structural Vim status.
338
444
 
339
445
  | Token | Renders |
340
446
  | --------------- | --------------------------------------------------------------------------------------------- |
341
- | `$model` | label selected by `editorModelLabel` (`id`, or name with ID fallback) |
447
+ | `$model` | label selected by `components.editor.modelLabel` (`id`, or name with ID fallback) |
342
448
  | `$model_id` | active Pi model ID |
343
449
  | `$model_name` | active Pi model display name; empty when no name is set |
344
450
  | `$provider` | provider label using Zentui's existing formatting |
@@ -351,13 +457,21 @@ Missing, non-string, or empty values use the default `$model $provider( $think
351
457
 
352
458
  ## Footer Format Template
353
459
 
354
- For full control, set a Starship-style `footerFormat` template string. It supports `$variable` and `${variable}` tokens, a special `$fill` token that splits the line into left and right zones, and conditional groups `( ... )` that drop entirely when every nested variable is empty. When set, it overrides the built-in `footerSegments` layout; when empty or omitted, the segment layout above is used.
460
+ For full control, set `components.footer.styles.starship.format` to a Starship-style template string. It supports `$variable` and `${variable}` tokens, a special `$fill` token that splits the line into left and right zones, and conditional groups `( ... )` that drop entirely when every nested variable is empty. When set, it overrides `components.footer.styles.starship.segments`; when empty or omitted, the segment layout above is used.
355
461
 
356
462
  A second `$fill` creates a **centered middle zone** — content between the two fills is true-centered (`floor((gap - middle) / 2)`), just like third-party statuses placed `middle`.
357
463
 
358
464
  ```json
359
465
  {
360
- "footerFormat": "$os $username $cwd($sep$session_name)( on $git_branch)( $git_status)( via $runtime)$fill($context)($sep$tokens)($sep$cost)($sep$time)"
466
+ "components": {
467
+ "footer": {
468
+ "styles": {
469
+ "starship": {
470
+ "format": "$os $username $cwd($sep$session_name)( on $git_branch)( $git_status)( via $runtime)$fill($context)($sep$tokens)($sep$cost)($sep$time)"
471
+ }
472
+ }
473
+ }
474
+ }
361
475
  }
362
476
  ```
363
477
 
@@ -365,10 +479,20 @@ Center the branch between directory and cost:
365
479
 
366
480
  ```json
367
481
  {
368
- "footerFormat": "$cwd $fill $git_branch $fill $cost"
482
+ "components": {
483
+ "footer": {
484
+ "styles": {
485
+ "starship": {
486
+ "format": "$cwd $fill $git_branch $fill $cost"
487
+ }
488
+ }
489
+ }
490
+ }
369
491
  }
370
492
  ```
371
493
 
494
+ The released flat `footerFormat` and `footerSegments` keys remain accepted only as legacy migration inputs.
495
+
372
496
  ### Variables
373
497
 
374
498
  | Token | Aliases | Renders |
@@ -384,7 +508,7 @@ Center the branch between directory and cost:
384
508
  | `$git_added` | | added line count (`+N`) |
385
509
  | `$git_deleted` | | deleted line count (`−N`) |
386
510
  | `$runtime` | | runtime icon + version |
387
- | `$model` | | selected model label (`editorModelLabel`) |
511
+ | `$model` | | selected model label (`components.footer.modelLabel`) |
388
512
  | `$provider` | | formatted provider label |
389
513
  | `$package` | | project package version, `is <glyph> <version>` (manifest-derived) |
390
514
  | `$package_version` | | raw project package version (no icon) |
@@ -414,16 +538,18 @@ Center the branch between directory and cost:
414
538
  - Literal text (`on branch`, `using`, `\|`, spaces) is rendered verbatim — you control all spacing.
415
539
  - Each variable renders its core value only (no `on`/`via` prefixes); add those words as literal text.
416
540
  - 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.
417
- - `$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.
541
+ - `$session_name` is available whenever `components.footer.styles.starship.format` is set, independently of `components.footer.styles.starship.segments.sessionName`; use a conditional group such as `($sep$session_name)` so unnamed sessions leave no separator.
418
542
  - The built-in wide footer appends cache totals to the token segment, `(sub)` to cost, and `(auto)` to context when available. Custom formats keep `$tokens`, `$cost`, and `$context` backward-compatible and include telemetry only through the atomic variables above.
419
- - `DEFAULT_COMPACT_FOOTER_FORMAT` omits model/provider and atomic telemetry. Add their variables explicitly to `compactFooterFormat` to opt in at narrow widths.
543
+ - `DEFAULT_COMPACT_FOOTER_FORMAT` omits model/provider and atomic telemetry. Add their variables explicitly to `components.footer.styles.starship.compactFormat` to opt in at narrow widths. The flat `compactFooterFormat` key is a legacy migration input.
420
544
  - Auto-compaction settings refresh on the next normal footer synchronization event. Unsupported Pi capabilities or settings-read errors safely omit optional markers.
421
545
  - Unknown `$variables` render empty.
422
546
  - Set or clear at runtime: `/zentui format "<template>"` and `/zentui format clear`.
423
547
 
424
548
  ## Fixed editor (experimental, opt-in)
425
549
 
426
- The fixed editor pins the Zentui editor and footer at the bottom of the terminal while the transcript scrolls above. This enables composing follow-up messages while referencing earlier conversation history.
550
+ The fixed layout pins Pi's usable editor cluster at the bottom of the terminal while the transcript scrolls above. It can activate independently of Zentui editor and Footer style; Pi compatibility inspection decides whether it is safe. Hidden contributes zero rows and no spacer. A live Footer style change tears down the compositor, replaces the Footer, and reprobes the new component; reprobe failure leaves ordinary rendering active.
551
+
552
+ At constrained heights, Zentui reserves one transcript row, keeps a complete Zentui editor frame and cursor, then prioritizes active autocomplete, Footer, below-editor widget, above-editor widget, and status. Editor content crops around the cursor; known autocomplete content crops around its selected item. Native and third-party editors are treated as opaque and are either pinned intact or rendered through Pi's normal layout. If the terminal later grows, fixed layout returns automatically without reinstalling the compositor.
427
553
 
428
554
  ### How to enable
429
555
 
@@ -435,8 +561,10 @@ Or in `~/.pi/agent/zentui.json`:
435
561
 
436
562
  ```json
437
563
  {
438
- "fixedEditor": {
439
- "enabled": true
564
+ "layout": {
565
+ "fixedEditor": {
566
+ "enabled": true
567
+ }
440
568
  }
441
569
  }
442
570
  ```
@@ -445,19 +573,27 @@ Or in `~/.pi/agent/zentui.json`:
445
573
 
446
574
  | Key | Action |
447
575
  | --- | ------ |
448
- | `PageUp` / `PageDown` | Scroll transcript one viewport up/down |
576
+ | `PageUp` / `PageDown` | Scroll transcript one viewport up/down; consumed at either boundary while transcript scrolling exists |
449
577
  | `Ctrl+Shift+↑` / `Ctrl+Shift+↓` | Scroll transcript up/down (Kitty protocol variants supported) |
450
578
  | `Enter` | Jump to bottom (and submit message) |
451
579
 
580
+ PageUp/PageDown propagate to Pi when there is no transcript scroll range, while normal-flow fallback is active, or while an overlay owns input.
581
+
582
+ ### Graceful cleanup
583
+
584
+ Live disable restores the full scroll region, mouse modes, alternate-scroll behavior, autowrap, cursor visibility, and primary screen before Pi repaints its normal layout. Pi lifecycle shutdown (`/quit`, Ctrl+C, Ctrl+D, SIGHUP, and SIGTERM) performs the same reset without a late repaint into the caller's shell. Cleanup after `SIGKILL`, a fatal crash that prevents Pi's shutdown callback, terminal disconnect, or failure of both terminal writers cannot be guaranteed.
585
+
452
586
  ### Mouse scroll (default on)
453
587
 
454
- Mouse wheel scrolling is enabled by default when the fixed editor is on. Disable it via `/zentui` Features or:
588
+ Mouse wheel scrolling is enabled by default when the fixed editor is on. Disable it from the `/zentui` **Layout** section or:
455
589
 
456
590
  ```json
457
591
  {
458
- "fixedEditor": {
459
- "enabled": true,
460
- "mouseScroll": true
592
+ "layout": {
593
+ "fixedEditor": {
594
+ "enabled": true,
595
+ "mouseScroll": false
596
+ }
461
597
  }
462
598
  }
463
599
  ```
@@ -469,8 +605,13 @@ Mouse wheel scrolling is enabled by default when the fixed editor is on. Disable
469
605
  - **Incompatible with** `pi-powerline-footer`, `@tifan/pi-fixed-editor`, and `pi-sticky-input`. These packages patch the same Pi TUI internals; only one rendering owner can be active at a time.
470
606
  - **Alternate screen**: Uses the terminal's alternate screen buffer. Native scrollback history is not accessible while the fixed editor is active.
471
607
  - **Pi version fragility**: Patches internal TUI methods (`doRender`, `render`, `terminal.write`, `terminal.rows`) that may change across Pi versions. If the TUI layout is unsupported, Zentui falls back to normal rendering with a console warning.
608
+ - Pi has no Footer getter or Footer-change event. Zentui observes normal third-party takeover through Pi's custom-component disposal callback and will not restore Native afterward. Direct internal mutation, a future Pi version that stops disposing replaced components, and automatic fixed-layout reprobe after third-party replacement cannot be detected.
472
609
  - If your terminal is stuck after a crash, run `reset` or restart the terminal.
473
610
 
611
+ ## Acknowledgments
612
+
613
+ The minimalist frame's information hierarchy was inspired by [VinhLe1410/pi-custom-input](https://github.com/VinhLe1410/pi-custom-input) and is integrated with Zentui's existing editor, state, configuration, and compatibility layers.
614
+
474
615
  ## Requirements
475
616
 
476
617
  - [Pi](https://pi.dev) coding agent 0.80.3 or newer