pi-zentui 0.27.0 → 0.28.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
@@ -162,13 +162,13 @@ Useful shortcuts:
162
162
  /zentui format clear
163
163
  ```
164
164
 
165
- **Thinking (Experimental)** uses one private `AssistantMessageComponent` renderer for Rail, Tree, and Streaming, tested against exact Pi versions 0.80.5, 0.82.1, 0.83.0, 0.84.0, 0.84.4, and 0.85.1. It is disabled by default and may break after Pi updates. Zentui installs an enabled startup mode before transcript restoration. A healthy installed controller lets active Streaming switch live to Rail or Tree, and lets Rail and Tree switch live between each other, without reinstalling its patch. Entering Streaming from Rail or Tree saves the choice but keeps the active structural mode until restart. Disabling live restores native thinking and releases Streaming resources. First enable and re-enable after a live disable are also restart-gated. Mode changes while disabled only preconfigure the next enable. Startup failures, missing constructors, incompatible private child layouts, parser limits, theme/render/width errors, and displaced patch ownership fail open to complete native thinking. If cleanup throws while leaving Streaming, disabling still restores native thinking and a structural selection still becomes active; the successful change warns that Streaming is unavailable for the rest of the session.
165
+ **Thinking (Experimental)** uses one private `AssistantMessageComponent` renderer for Rail, Tree, and Streaming, tested against exact Pi versions 0.85.0 and 0.87.1. It is disabled by default and may break after Pi updates. Zentui installs an enabled startup mode before transcript restoration. A healthy installed controller lets active Streaming switch live to Rail or Tree, and lets Rail and Tree switch live between each other, without reinstalling its patch. Entering Streaming from Rail or Tree saves the choice but keeps the active structural mode until restart. Disabling live restores native thinking and releases Streaming resources. First enable and re-enable after a live disable are also restart-gated. Mode changes while disabled only preconfigure the next enable. Startup failures, missing constructors, incompatible private child layouts, parser limits, theme/render/width errors, and displaced patch ownership fail open to complete native thinking. If cleanup throws while leaving Streaming, disabling still restores native thinking and a structural selection still becomes active; the successful change warns that Streaming is unavailable for the rest of the session.
166
166
 
167
167
  Rail shows every parsed label in each native contiguous thinking run (`│ Label`, with only the open final phase shown as `│ • Label`). Tree independently shows the latest five labels in each run (`├─ · Label`, settled `└─ · Label`, open `└─ • Label`); it never aggregates across intervening text or tool blocks. Rail and Tree follow Pi's thinking visibility. Complete strict SGR styling is stripped before parsing; every other terminal control and unsafe or unstructured content keeps the complete run native. Labels are rendered by fresh host-shaped Pi Markdown instances before cropping, so emphasis, code, links, HTML, LaTeX, custom transforms, and native `thinkingText` styling remain host-controlled. Every label occupies one terminal row: ANSI/OSC/grapheme-aware cropping adds `…` only when needed. Native horizontal padding stays external. Connectors are styled directly with the current theme's `accent` callback on every render, so custom themes control them independently. Hidden native thinking remains hidden and keeps Pi's native hidden label.
168
168
 
169
169
  Streaming retains Pi's host-rendered final five rows under `Thinking 7.1s`, folds completed reasoning under `Thought` or current-session `Thought for 12.3s`, and owns the configured thinking-toggle binding (Ctrl+T by default) only when started in Streaming. Its input listener and timer are acquired only for an enabled Streaming session start; startup acquisition failure uses native thinking and marks Streaming unavailable. Restored completions cannot recover a duration because Pi does not persist the thinking-end timestamp. Expand/refold and lifecycle tracking are bounded to 256 retained assistant components; evicted entries are first restored natively. All modes restore/dispose on shutdown. Thinking (Experimental) never writes the Working line and does not change its existing **Thinking time** option, working text, Footer, Editor, statuses, or model behavior.
170
170
 
171
- Pi 0.84 also provides a native fullscreen TUI with a sticky editor and Footer. Pi 0.84.4 is covered by a fullscreen live-transition PTY smoke in addition to the standard matrix. Zentui does not enable fullscreen automatically; select it from Pi's `/settings`, set `"tuiMode": "fullscreen"` in Pi settings, or launch Pi with `--tui-mode fullscreen`.
171
+ Pi 0.84 also provides a native fullscreen TUI with a sticky editor and Footer. Both tested versions are covered by a fullscreen live-transition PTY smoke in addition to the standard matrix. Zentui does not enable fullscreen automatically; select it from Pi's `/settings`, set `"tuiMode": "fullscreen"` in Pi settings, or launch Pi with `--tui-mode fullscreen`.
172
172
 
173
173
  **Codex quota (opt-in):** Show remaining 5-hour/weekly quota for `openai-codex` through independent Editor and Starship Footer settings, both off by default. See [configuration and private-endpoint limitations](./docs/configuration.md#codex-account-quota), including `$codex_quota` for custom templates.
174
174
 
@@ -176,7 +176,7 @@ Minimalist can also show the latest assistant prompt's cache hit rate in its top
176
176
 
177
177
  ## Requirements
178
178
 
179
- - [Pi](https://pi.dev) coding agent 0.80.5 or newer
179
+ - [Pi](https://pi.dev) coding agent 0.85.0 or newer
180
180
  - A [Nerd Font](https://www.nerdfonts.com/) for Nerd icons; Auto falls back to ASCII when no supported terminal signal is present
181
181
 
182
182
  ## Development
@@ -422,7 +422,7 @@ Reference only—not a starter file. Prefer the minimal overrides above. Optiona
422
422
  - Editor `modelLabel` uses `id` by default; `name` uses the display name with ID fallback. Footer has an independent `modelLabel` control.
423
423
  - `components.userMessages` owns User-message enablement, `framed | framed-copy-friendly | compact | labeled` style selection, and color source. Disabling it delegates byte-for-byte to Pi's native renderer.
424
424
  - `components.thinkingSteps` independently owns opt-in **Thinking (Experimental)** display. It defaults to `{ "enabled": false, "mode": "tree" }`; canonical modes are `rail | tree | streaming`. The former persisted `streaming-experimental` value is accepted only as a migration alias and is normalized to `streaming` on save.
425
- - 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.
425
+ - All three modes decorate Pi's private host renderer and are tested on exact Pi versions 0.85.0 and 0.87.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.
426
426
  - `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.
427
427
  - `components.workingLine.placement` (`above | border`) selects the default floating row or the Minimalist/Opencode top border. Accent Rail supports only Above. Disabled, native, unavailable, or unsafe/narrow editors and Pi versions without working-row visibility control fall back to Above without changing saved choices. Legacy `input` normalizes to `above`; the prompt remains available for typing.
428
428
  - `components.selectorBorders` owns selector-border enablement, fixed `zentui` style, and color source. Disable it for native Pi behavior.
@@ -456,7 +456,7 @@ This example enables only Editor quota, without changing any component's style o
456
456
 
457
457
  **Custom templates remain authoritative.** Saved nonempty formats, including copies of old defaults, are never rewritten or augmented outside the template. Add `( $codex_quota)` to either Opencode variant's `metadataFormat`, `($sep$codex_quota)` to a Footer wide `format`, or `$wrap_sep($codex_quota)` to `compactFormat`. Tokens remain empty when the corresponding quota toggle is off or the provider is ineligible. Unlike ordinary Footer `segments` flags, quota consent cannot be bypassed by a template.
458
458
 
459
- **Compatibility and privacy:** Zentui uses Pi's public active-model metadata, `modelRegistry.getProvider("openai-codex")`, and `getProviderAuth("openai-codex")`, available on tested Pi 0.84.0 and 0.85.1. Hosts without safe model/provider routing metadata do not collect or display quota; unavailable auth shows placeholders. There is no fallback to private storage or older credential APIs; the overall Pi minimum is unchanged. Auth is sent only to `https://chatgpt.com/backend-api/wham/usage`, with redirects rejected. JWT decoding is limited to the account-routing claim and is not identity verification. An opaque credential change invalidates cached data conservatively.
459
+ **Compatibility and privacy:** Zentui uses Pi's public active-model metadata, `modelRegistry.getProvider("openai-codex")`, and `getProviderAuth("openai-codex")`, available on supported Pi 0.85.0 and 0.87.1. Hosts without safe model/provider routing metadata do not collect or display quota; unavailable auth shows placeholders. There is no fallback to private storage or older credential APIs; the Pi minimum is 0.85.0. Auth is sent only to `https://chatgpt.com/backend-api/wham/usage`, with redirects rejected. JWT decoding is limited to the account-routing claim and is not identity verification. An opaque credential change invalidates cached data conservatively.
460
460
 
461
461
  The endpoint is undocumented and may change or reject some plans. Its path and seconds-based window field are corroborated by [OpenAI's Codex client](https://github.com/openai/codex/blob/rust-v0.98.0/codex-rs/backend-client/src/client.rs); this is not a public API guarantee. Automated verification uses synthetic responses, not a live account. No reset times, countdowns, alerts, or quota history are provided.
462
462
 
@@ -524,10 +524,6 @@ Set `components.editor.style` to `accent-rail` or select **Accent Rail** in `/ze
524
524
 
525
525
  Known autocomplete rows retain Pi's native text, descriptions, and scrolling on the same full-width surface. The selected native `→` becomes the configured rail without replacing Pi's selected-text color. Ambiguous third-party editor layouts fail open using already-rendered native rows.
526
526
 
527
- In fullscreen Pi 0.84.x, Zentui applies a private, shape-checked layout workaround only to the active owned one-row Accent Rail editor. Pi's dock currently reserves a three-row minimum for its bordered native editor; the workaround preserves that minimum but positions a one-row rail as `[blank, rail]`, leaving Pi's final padding row before the independent Footer. Other editor styles, regular mode, multiline input, viewport indicators, and autocomplete fail open unchanged. Unsupported Pi versions or changed internal shapes skip the workaround. Remove this compatibility path when [upstream Pi's fullscreen dock](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/modes/interactive/interactive-mode.ts) exposes composer minimum-size control or no longer hard-codes a three-row editor minimum. This link identifies the upstream source seam; it does not imply an upstream issue exists.
528
-
529
- Set `ZENTUI_DEBUG=1` when launching Pi to log the workaround diagnostic without adding normal UI noise. Maintainers can probe an explicitly installed compatible host with `ZENTUI_TEST_GLOBAL_PI=/path/to/pi npx vitest run test/accent-rail-layout-patch.test.ts -t "explicitly selected"`.
530
-
531
527
  `transparent` defaults to `false`. Set it to `true` or select **Transparent** in `/zentui` to remove only Zentui-owned input and autocomplete backgrounds while preserving geometry, rail/text colors, and native autocomplete backgrounds. The rail and gap are rendered decoration, not underlying prompt text; terminal drag or rectangular selection can still include them.
532
528
 
533
529
  ### Minimalist
@@ -538,6 +534,128 @@ Path examples are `src` (`compact`), `zentui/src` (`project`), and `~/Projects/z
538
534
 
539
535
  Autocomplete stays inside the frame when Pi output can be split safely. Unknown third-party layouts fail open. Footer visibility remains independently controlled by `components.footer.style`; Minimalist does not remove Pi's header.
540
536
 
537
+ ### Minimalist metadata templates and custom values
538
+
539
+ Minimalist has six independently configurable border slots under
540
+ `components.editor.styles.minimalist.formats`: `topLeft`, `topMiddle`,
541
+ `topRight`, `bottomLeft`, `bottomMiddle`, and `bottomRight`. Missing slots keep
542
+ Zentui's generated layout; an empty string hides that slot's configurable
543
+ metadata. No extra rows are added. Use **Editor → Metadata templates** in
544
+ `/zentui` to edit a slot or **Reset / inherit** to delete its override.
545
+
546
+ Templates use the existing `$variable`, `${variable}`, literal text, and
547
+ conditional `( ... )` grammar. Layout is selected by the six slot names, not
548
+ `$fill`. Viewport counts, Bash mode, and an embedded Working line remain
549
+ operational indicators outside the templates. Explicit templates may show
550
+ metadata whose ordinary visibility toggle is off, but can never bypass Editor
551
+ Codex quota consent. Nothing is appended outside an explicit template.
552
+
553
+ Custom values are published by other extensions. Give them simple local names
554
+ with `variables`, then reference those names in any slot:
555
+
556
+ ```json
557
+ {
558
+ "components": {
559
+ "editor": {
560
+ "style": "minimalist",
561
+ "styles": {
562
+ "minimalist": {
563
+ "variables": { "claude_quota": "@scope/usage:quota" },
564
+ "formats": {
565
+ "topMiddle": "$claude_quota",
566
+ "bottomMiddle": "$session_name"
567
+ },
568
+ "extensionColorMode": "original"
569
+ }
570
+ }
571
+ }
572
+ }
573
+ }
574
+ ```
575
+
576
+ **Editor → Custom variable aliases** edits the name-to-publisher-key JSON or
577
+ resets the aliases. Names use letters, digits, and underscores, start with a
578
+ letter or underscore, and cannot replace built-in/structural variables or
579
+ prototype names. At most 16 aliases are accepted; keys are nonempty, at most
580
+ 64 UTF-16 code units, and contain no whitespace or terminal controls. Reset
581
+ removes only the edited overrides. Unrelated owners/styles and unknown raw
582
+ configuration remain untouched. These settings never enable Footer or Working
583
+ line.
584
+
585
+ Built-ins are `$model`, `$model_id`, `$model_name`, `$provider`, `$thinking`,
586
+ `$session_name`, `$turn_duration`, `$cost`, `$context`, `$cache_hit`,
587
+ `$codex_quota`, `$cwd`, `$git_branch`, `$git_status`, `$tokens`, `$input_tokens`,
588
+ and `$output_tokens`. `$turn_duration` is current/completed interaction time,
589
+ not total session duration. Existing path, context, separator and color
590
+ preferences still apply. `$sep` (alias `$separator`) uses the configured
591
+ Minimalist dash/dot separator. Prefer optional groups such as
592
+ `$model($sep$thinking)` to avoid separators around empty values.
593
+
594
+ `$extensions` aggregates published custom values in deterministic key order.
595
+ The generated top-right layout includes it after cost; setting an explicit
596
+ slot lets you move or omit it. Values referenced through an alias anywhere in
597
+ the six effective templates are excluded from the aggregate, avoiding an
598
+ automatic duplicate. This is distinct from Footer's existing extension-status
599
+ integration. Custom values do not automatically consume `ctx.ui.setStatus()`.
600
+
601
+ Centers are terminal-centered and clamped between side labels; they disappear
602
+ whole when they cannot fit. Custom values yield whole before built-in side
603
+ metadata at narrow widths, rather than clipping quotas or activity labels.
604
+ Large aggregates yield as one unit. Literal separators are template-owned;
605
+ use conditional groups around optional values. Width cannot guarantee that
606
+ all configured metadata is visible.
607
+
608
+ **Custom value colors** offers Original (default) and Zentui. Original retains
609
+ safe SGR and HTTP(S) links from the publisher, with closing resets so styles
610
+ cannot leak into borders. Zentui strips publisher styling and applies the
611
+ Editor color source and extension-status fallback style. Newlines and other
612
+ terminal controls are sanitized; no publisher callbacks or commands run while
613
+ rendering.
614
+
615
+ #### Custom-value publisher protocol v1
616
+
617
+ The versioned event-bus protocol publishes data, not a location. Probe before
618
+ each refresh and keep the extension's normal fallback when inactive:
619
+
620
+ ```typescript
621
+ const key = "@scope/usage:quota";
622
+ const capability = { supported: false, active: false, key };
623
+ pi.events.emit("zentui:variable-capability", capability);
624
+
625
+ if (capability.active) {
626
+ pi.events.emit("zentui:variable", { key, text: "CC $459/1200" });
627
+ ctx.ui.setStatus(key, undefined); // remove this publisher's own fallback
628
+ } else {
629
+ ctx.ui.setStatus(key, "CC $459/1200");
630
+ }
631
+
632
+ // Remove a value on publisher shutdown or when no longer applicable.
633
+ pi.events.emit("zentui:variable", { key, text: undefined });
634
+ ```
635
+
636
+ Zentui adds `version: 1` and `supported: true` to a mutable probe. Optional
637
+ `key` tests demand for that particular publication; without it, `active`
638
+ reports whether any custom value is demanded. Active requires the current TUI
639
+ session and either an owned, enabled, safely decorated Minimalist/Opencode
640
+ editor with a referencing alias or aggregate, or an owned Starship Footer with
641
+ a referencing alias in its wide or responsive compact template. Availability
642
+ is the union of independently owned consumers: disabling Editor never drops a
643
+ value still demanded by Footer. It is not a guarantee of visibility at the
644
+ current width. Startup probes may precede the first safe
645
+ editor render: probe again on the publisher's next refresh. This protocol
646
+ adds no Zentui poller and no capability-change notification.
647
+
648
+ Use stable package-qualified keys. The namespace is global by convention;
649
+ collisions are last-update-wins, and either publisher can remove a shared key.
650
+ At most 16 unique values are retained, with keys up to 64 and raw text up to
651
+ 256 UTF-16 code units. Malformed/over-limit updates are ignored; existing
652
+ keys can still update at capacity. Empty text also removes. Positive updates
653
+ while inactive are ignored. Session replacement, shutdown, disable, loss of
654
+ ownership, or loss of template demand drops inactive values; publishers must
655
+ republish after reactivation. Publishers own their refresh resources, data
656
+ freshness, and stale markers. Zentui neither fetches account data nor persists
657
+ these values. Working-line protocol v1 remains unchanged.
658
+
541
659
  ### Opencode completion menu
542
660
 
543
661
  Both Opencode variants default to `completionMenu: "palette"` and can be configured independently. The transparent palette keeps captured native rows and embedded backgrounds, removes only a recognized selected `→` while preserving native emphasis, omits a narrowly recognized trailing count row such as `(1/47)`, fills available width without adding a background, and adds a bottom separator plus `↑↓ Navigate Enter Use Esc Close`.
@@ -592,6 +710,48 @@ The configured right zone and Pi's operational right status are right-aligned to
592
710
 
593
711
  `$context` uses Pi's current context snapshot and the live assistant context override, refreshing on the existing 250 ms streaming render cadence. `$tokens` and `$cache_hit` use authoritative persisted session snapshots, so they update at normal session synchronization boundaries rather than estimating in-progress totals. These variables are independent of Footer visibility, style, color source, and configuration.
594
712
 
713
+ ### Custom values in Opencode metadata
714
+
715
+ Both Opencode styles can reference the same event-bus values used by Minimalist.
716
+ Each style has independent `variables` and `extensionColorMode` settings:
717
+
718
+ ```json
719
+ {
720
+ "components": {
721
+ "editor": {
722
+ "styles": {
723
+ "opencode": {
724
+ "variables": { "quota": "@scope/usage:quota" },
725
+ "metadataFormat": "$model( · $quota)$fill($session_name)",
726
+ "extensionColorMode": "original"
727
+ },
728
+ "opencode-copy-friendly": {
729
+ "variables": { "quota": "@scope/usage:quota" },
730
+ "metadataFormat": "$model$fill($quota)"
731
+ }
732
+ }
733
+ }
734
+ }
735
+ }
736
+ ```
737
+
738
+ Aliases and these formats are JSON-only for Opencode; Minimalist's settings
739
+ controls still edit only Minimalist. Missing color mode means Original; Zentui
740
+ uses Editor's color source and the historical extension-status fallback style.
741
+ Custom values yield whole before built-in metadata when the row cannot fit.
742
+ No value is automatically appended to an explicit format. Enabling/disabling
743
+ Editor or changing a format never changes Footer choices.
744
+
745
+ Starship independently supports aliases under
746
+ `components.footer.styles.starship.variables` and Original/Zentui under
747
+ `extensionColorMode`; both wide `format` and responsive `compactFormat` can
748
+ reference them. See [custom Footer values](./footer-format.md#custom-extension-values).
749
+ Native/Hidden Footer and existing `setStatus` placement/color choices are
750
+ unchanged. Footer's compact `$extensions` continues to mean Pi's keyed extension
751
+ statuses, not this new variable registry. Removing an alias/format reference
752
+ releases only that consumer's demand; values survive while any other owned
753
+ consumer still references them.
754
+
595
755
  Model variables use Editor `colors.model` (legacy `editorModel`), provider uses `colors.provider` (legacy `editorProvider`), and thinking uses the matching Editor level style. Literal text, session name, and usage metadata use the neutral editor-border theme style. ANSI/VT sequences, controls, and line-breaking whitespace are sanitized without collapsing ordinary spaces.
596
756
 
597
757
  Missing, non-string, or empty values use `$model $provider( $thinking)( $codex_quota)`, with identical spacing while quota is off. A non-empty format that resolves to no metadata preserves the normal blank spacer and metadata rows. This option is JSON-only; `/zentui format` controls the Footer.
@@ -624,7 +784,7 @@ Only an actually open thinking phase uses `•`; a text/tool transition or resto
624
784
 
625
785
  Streaming keeps the reviewed host-rendered behavior: while open it shows the latest five rendered terminal rows beneath `Thinking 7.1s`; completion folds under `Thought` or current-session `Thought for Ns`. Restored entries have no duration because Pi does not persist a reliable thinking-end timestamp. Only a session started in active Streaming owns its validated configured `app.thinking.toggle` binding and one-second timer. Ctrl+T expands/refolds native reasoning. Startup resource failures and private-shape/render failures use complete native thinking. A cleanup callback that throws while leaving Streaming is contained: Rail or Tree remains active, while Streaming becomes unavailable for that session. Component and timing tracking are bounded to 256; evicted entries are restored natively first.
626
786
 
627
- The exact all-mode private matrix covers Pi 0.80.5, 0.82.1, 0.83.0, 0.84.0, 0.84.4, and 0.85.1 under dark, light, and current themes, narrow/wide widths and resize; Pi 0.84.4 also has a fullscreen live-transition PTY smoke. Thinking (Experimental) never owns or writes the Working line, including its unchanged **Thinking time** option, and does not own Footer, Editor, widgets, statuses, or model behavior.
787
+ The exact all-mode private matrix covers Pi 0.85.0 and 0.87.1 under dark, light, and current themes, narrow/wide widths and resize; both versions also have fullscreen live-transition PTY coverage. Thinking (Experimental) never owns or writes the Working line, including its unchanged **Thinking time** option, and does not own Footer, Editor, widgets, statuses, or model behavior.
628
788
 
629
789
  ## Working line
630
790
 
@@ -645,7 +805,9 @@ Classic and KITT move color across message and segments. **Animate spinner color
645
805
 
646
806
  Both speeds accept `30..1000` ms. Classic/KITT combine both cadences through one Pi Loader interval; exact cycles are used within 1024-frame/512-KiB limits. Pathological custom pairs use a bounded evenly distributed schedule with at most half a spinner-cycle and half a text-step rounding. Legacy `intervalMs` is accepted only as migration input for `spinnerIntervalMs` when the canonical field is absent.
647
807
 
648
- Content reserves the complete Tokens label and active extension segments first, then Message, Thought, Elapsed, and Tool allocation, while preserving visual order **Message · Tool · Elapsed · Thought · Tokens · Extensions** within the 80-column Loader-row contract. Active thought starts as `thinking 0s`; completed positive thought becomes `thought for Ns`. Rebuilds preserve spinner and visible color phase. Pi's working-row APIs are global and unkeyed, so another extension may win by writing last.
808
+ Content reserves the complete Tokens label and active extension segments first, then Message, Thought, Elapsed, and Tool allocation, while preserving visual order **Message · Tool · Elapsed · Thought · Tokens · Extensions** within the 80-column Loader-row contract. Active thought starts as `thinking 0s`; completed positive thought becomes `thought for Ns`. Rebuilds preserve spinner and visible color phase.
809
+
810
+ Pi's working-row APIs are global and unkeyed. While owning the row, Zentui reasserts its blank message on owned refreshes/reconciles (even with unchanged frames) and before a Border fallback reveals the Above row. A later external message write or reset can still win until the next such boundary; native spinner ticks alone do not repair it, and there is no added polling. Separate multiline widgets are unaffected; extensions sharing the working-message slot should use keyed segments below instead.
649
811
 
650
812
  ### Working-line extension integration
651
813
 
@@ -774,7 +936,7 @@ Pi 0.84 adds a native fullscreen TUI with sticky Editor and Footer plus an indep
774
936
  }
775
937
  ```
776
938
 
777
- Save this in Pi's `~/.pi/agent/settings.json`, select fullscreen in Pi's `/settings`, or use `--tui-mode fullscreen`. Zentui does not enable it automatically. Pi owns layout and scrolling while Zentui supplies configured components. Pi 0.80.5–0.83 remain supported for styling without native sticky placement.
939
+ Save this in Pi's `~/.pi/agent/settings.json`, select fullscreen in Pi's `/settings`, or use `--tui-mode fullscreen`. Zentui does not enable it automatically. Pi owns layout and scrolling while Zentui supplies configured components. Zentui requires Pi 0.85.0 or newer.
778
940
 
779
941
  ## Compatibility and migration
780
942
 
@@ -158,6 +158,57 @@ Example:
158
158
 
159
159
  These tokens are structural in compact mode: `$wrap` and `$wrap_sep` render no text themselves, and `$extensions` is empty when no third-party status is active.
160
160
 
161
+ ## Custom extension values
162
+
163
+ Publishers can provide named text through `zentui:variable` protocol v1, without
164
+ owning or wrapping Footer. Map stable publisher keys to simple local names under
165
+ `components.footer.styles.starship.variables`, then reference the names in either
166
+ template:
167
+
168
+ ```json
169
+ {
170
+ "components": {
171
+ "footer": {
172
+ "styles": {
173
+ "starship": {
174
+ "variables": { "quota": "@scope/usage:quota" },
175
+ "extensionColorMode": "original",
176
+ "format": "$cwd$fill($quota)($sep$cost)",
177
+ "compactFormat": "$cwd$fill($quota)$wrap_sep$tokens"
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ ```
184
+
185
+ These JSON-only aliases cannot replace built-in variables, aliases such as
186
+ `branch`, structural tokens, or prototype names. Missing values are empty;
187
+ conditional groups remove associated optional text. No custom value is appended
188
+ outside an explicit template. A compact-only reference creates demand only
189
+ while responsive mode is enabled. Native and Hidden never acquire this registry
190
+ on behalf of Footer, and configuring aliases does not select Starship or enable
191
+ Editor.
192
+
193
+ Original is the default color mode and preserves permitted publisher SGR and
194
+ HTTP(S) hyperlinks. Zentui strips incoming styling and uses Footer's own
195
+ `colors.extensionStatus` role and color source. These choices are independent of
196
+ Editor and of the existing `extensionStatuses.colorModes` preferences.
197
+
198
+ Values are kept whole through wide alignment, reflow, and compact packing.
199
+ When a value would be partially cropped or split, Footer recomposes without it
200
+ instead of showing misleading partial quotas or labels. Width fitting uses safe
201
+ internal probes that are restored before terminal output. Built-in narrow-width
202
+ behavior remains unchanged, and template literals still own their spacing.
203
+
204
+ The compact `$extensions` token above its existing configuration section
205
+ continues to expand `ctx.ui.setStatus()` statuses; it is **not** an aggregate of
206
+ new custom values. A publisher can use `setStatus()` as its own fallback when
207
+ capability is inactive, and must remove only its own fallback when active to
208
+ avoid duplicate output. See [publisher protocol](./configuration.md#custom-value-publisher-protocol-v1)
209
+ for capability, bounds, session lifecycle, and ownership rules. Disabling one
210
+ surface does not evict a value still consumed by another owned surface.
211
+
161
212
  ## `$fill` behavior
162
213
 
163
214
  In the wide template:
@@ -3,6 +3,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import type { ZentuiConfig } from "./config";
4
4
  import { sanitizeEditorMetadataText } from "./editor-metadata-format";
5
5
  import { collectFooterFormatReferences, parseFooterFormat } from "./footer-format";
6
+ import { minimalistTemplateReferences } from "./minimalist-template";
6
7
 
7
8
  const INTERVAL = 60_000;
8
9
  const TIMEOUT = 10_000;
@@ -45,8 +46,9 @@ export function parseCodexQuota(value: unknown): CodexQuota | undefined {
45
46
  export function editorWantsCodexQuota(config: ZentuiConfig): boolean {
46
47
  const editor = config.components.editor;
47
48
  if (!editor.enabled || !editor.codexQuota) return false;
49
+ if (editor.style === "minimalist")
50
+ return minimalistTemplateReferences(editor.styles.minimalist).has("codex_quota");
48
51
  return (
49
- editor.style === "minimalist" ||
50
52
  editor.style === "accent-rail" ||
51
53
  collectFooterFormatReferences(
52
54
  parseFooterFormat(sanitizeEditorMetadataText(editor.styles[editor.style].metadataFormat)),