pi-zentui 0.17.0 → 0.18.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
@@ -4,7 +4,7 @@ A Starship-inspired statusline and Opencode-style TUI for [Pi](https://pi.dev).
4
4
 
5
5
  ## Screenshots
6
6
 
7
- ![Zentui](https://raw.githubusercontent.com/lmilojevicc/pi-zentui/main/assets/zentui.png)
7
+ ![Screenshot of Zentui with a framed user message, spacious Opencode Editor, model metadata, and Starship Footer.](./assets/main-cover.png)
8
8
 
9
9
  ## What is this?
10
10
 
@@ -14,7 +14,7 @@ Zentui styles three major Pi surfaces independently:
14
14
  - **User messages** — selectable framed, framed copy-friendly, compact, and labeled transcript messages
15
15
  - **[Starship](https://starship.rs/) footer** — current directory, Git, runtime, context, tokens, cost, and other configurable segments
16
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.
17
+ Editor, User messages, and selector borders use an `enabled` field. Footer uses one `style`: `native`, `starship`, or `hidden`. **Appearance** contains selector-border and icon settings.
18
18
 
19
19
  ## Features
20
20
 
@@ -39,19 +39,20 @@ Editor, User messages, and selector borders use an `enabled` field. Footer uses
39
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
40
  - Opencode autocomplete rows retain Pi's original unframed trailing layout; Minimalist keeps autocomplete inside its rounded frame
41
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
42
 
44
43
  Editor previews:
45
44
 
46
- ```text
47
- opencode opencode-copy-friendly minimalist
48
- ──────────────────── ──────────────────── ╭─ session ── model
49
-
50
- prompt › prompt │ prompt │
51
-
52
- metadata metadata ╰─ git ───── path ──╯
53
- ──────────────────── ────────────────────
54
- ```
45
+ <h4 align="center"><code>opencode</code></h4>
46
+
47
+ ![Zentui Opencode editor with an accent rail, model metadata, Nerd Font Git branch, and Starship footer.](./assets/screenshots/editor-opencode.png)
48
+
49
+ <h4 align="center"><code>opencode-copy-friendly</code></h4>
50
+
51
+ ![Zentui copy-friendly Opencode editor with model metadata, Nerd Font Git branch, and Starship footer.](./assets/screenshots/editor-opencode-copy-friendly.png)
52
+
53
+ <h4 align="center"><code>minimalist</code></h4>
54
+
55
+ ![Zentui Minimalist editor with session, cost, model, Git, and path metadata in a rounded frame with the Footer hidden.](./assets/screenshots/editor-minimalist.png)
55
56
 
56
57
  ### User messages
57
58
 
@@ -62,20 +63,23 @@ opencode opencode-copy-friendly minimalist
62
63
  - Disabling User-message styling delegates byte-for-byte to Pi's native renderer; native is not a style ID
63
64
  - No custom `plain` message style is provided
64
65
 
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
- ```
66
+ User-message previews:
67
+
68
+ <h4 align="center"><code>framed</code></h4>
69
+
70
+ ![Zentui Framed user-message style with horizontal borders, spacer rows, and an accent rail.](./assets/screenshots/user-message-framed.png)
71
+
72
+ <h4 align="center"><code>framed-copy-friendly</code></h4>
73
+
74
+ ![Zentui copy-friendly Framed user-message style with horizontal borders, spacer rows, and a copyable left edge.](./assets/screenshots/user-message-framed-copy-friendly.png)
75
+
76
+ <h4 align="center"><code>compact</code></h4>
77
+
78
+ ![Zentui Compact user-message style with a slim accent rail and no surrounding borders.](./assets/screenshots/user-message-compact.png)
79
+
80
+ <h4 align="center"><code>labeled</code></h4>
81
+
82
+ ![Zentui Labeled user-message style in a rounded frame with the label User.](./assets/screenshots/user-message-labeled.png)
79
83
 
80
84
  ### Git Status Icons
81
85
 
@@ -170,16 +174,15 @@ pi install git:github.com/lmilojevicc/pi-zentui
170
174
 
171
175
  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.
172
176
 
173
- The interactive `/zentui` menu is split into exactly eight component-oriented sections, in this order. Use `Tab` and `Shift+Tab` to switch sections:
177
+ The interactive `/zentui` menu is split into exactly seven component-oriented sections, in this order. Use `Tab` and `Shift+Tab` to switch sections:
174
178
 
175
179
  1. **Appearance** — selector-border enablement, style, and colors; icon mode.
176
180
  2. **Editor** — editor enablement, style, colors, model label, border behavior, viewport indicators, and settings for the selected editor style.
177
181
  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.
182
+ 4. **Footer** — `Native | Starship | Hidden` style selection. Starship additionally shows colors, model label, responsive layout, separator, context style, and path display.
183
+ 5. **Segments** — visibility toggles for non-Git Starship segments.
184
+ 6. **Git** — Starship Git segment and probe controls.
185
+ 7. **Extensions** — Starship extension-status placement and color controls for active keys.
183
186
 
184
187
  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.
185
188
 
@@ -199,13 +202,9 @@ Useful slash-command shortcuts:
199
202
  /zentui statusline toggle
200
203
  /zentui messages
201
204
  /zentui user-messages
202
- /zentui layout
203
205
  /zentui viewport-indicators enable
204
206
  /zentui viewport-indicators disable
205
207
  /zentui viewport-indicators toggle
206
- /zentui fixed-editor enable
207
- /zentui fixed-editor disable
208
- /zentui fixed-editor toggle
209
208
  /zentui format "$cwd on branch $git_branch$git_status using $runtime $fill $context"
210
209
  /zentui format clear
211
210
  ```
@@ -323,13 +322,6 @@ Default config values — copy this and change any value you want:
323
322
  }
324
323
  }
325
324
  },
326
- "layout": {
327
- "fixedEditor": {
328
- "enabled": false,
329
- "mouseScroll": true,
330
- "copyNotice": true
331
- }
332
- },
333
325
  "icons": {
334
326
  "mode": "auto",
335
327
  "cwd": "",
@@ -394,10 +386,9 @@ Default config values — copy this and change any value you want:
394
386
  - `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
387
  - `components.selectorBorders`: owns selector-border enablement, the fixed `zentui` style, and its color source.
396
388
  - `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
389
  - Editor and Footer `modelLabel` values are independent and have separate controls in the **Editor** and **Footer** sections.
399
390
  - 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.
391
+ - Flat released keys such as `editorStyle`, `features`, and `footerFormat` 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` paths are the primary JSON interface, and component saves materialize canonical snapshots.
401
392
  - 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
393
  - 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
394
  - `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.
@@ -417,7 +408,7 @@ The Minimalist editor is inspired by [pi-custom-input](https://github.com/VinhLe
417
408
 
418
409
  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
410
 
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.
411
+ 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.
421
412
 
422
413
  ## Editor Metadata Format
423
414
 
@@ -545,68 +536,17 @@ The released flat `footerFormat` and `footerSegments` keys remain accepted only
545
536
  - Unknown `$variables` render empty.
546
537
  - Set or clear at runtime: `/zentui format "<template>"` and `/zentui format clear`.
547
538
 
548
- ## Fixed editor (experimental, opt-in)
549
-
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.
553
-
554
- ### How to enable
539
+ ## Pi fullscreen mode
555
540
 
556
- ```text
557
- /zentui fixed-editor enable
558
- ```
559
-
560
- Or in `~/.pi/agent/zentui.json`:
561
-
562
- ```json
563
- {
564
- "layout": {
565
- "fixedEditor": {
566
- "enabled": true
567
- }
568
- }
569
- }
570
- ```
571
-
572
- ### Keyboard controls
573
-
574
- | Key | Action |
575
- | --- | ------ |
576
- | `PageUp` / `PageDown` | Scroll transcript one viewport up/down; consumed at either boundary while transcript scrolling exists |
577
- | `Ctrl+Shift+↑` / `Ctrl+Shift+↓` | Scroll transcript up/down (Kitty protocol variants supported) |
578
- | `Enter` | Jump to bottom (and submit message) |
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
-
586
- ### Mouse scroll (default on)
587
-
588
- Mouse wheel scrolling is enabled by default when the fixed editor is on. Disable it from the `/zentui` **Layout** section or:
541
+ Pi 0.84 introduces a native fullscreen TUI with a sticky editor and Footer plus an independently scrollable transcript. Enable it in Pi's `~/.pi/agent/settings.json`:
589
542
 
590
543
  ```json
591
544
  {
592
- "layout": {
593
- "fixedEditor": {
594
- "enabled": true,
595
- "mouseScroll": false
596
- }
597
- }
545
+ "tuiMode": "fullscreen"
598
546
  }
599
547
  ```
600
548
 
601
- **Warning**: Mouse scroll enables SGR mouse reporting, which disables native terminal text selection, URL click-through, and tmux/Herdr scrollback for the Pi session. Toggle off if you need those features.
602
-
603
- ### Conflicts and limitations
604
-
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.
606
- - **Alternate screen**: Uses the terminal's alternate screen buffer. Native scrollback history is not accessible while the fixed editor is active.
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.
609
- - If your terminal is stuck after a crash, run `reset` or restart the terminal.
549
+ You can also select fullscreen from Pi's `/settings` UI or start Pi with `--tui-mode fullscreen`. Zentui does not enable fullscreen automatically: Pi owns terminal layout, scrolling, and sticky placement, while Zentui supplies the configured editor and Footer components. Pi 0.80.3–0.83 remain supported for Zentui styling, without native sticky placement.
610
550
 
611
551
  ## Acknowledgments
612
552
 
@@ -97,12 +97,6 @@ export type FooterSegmentsConfig = {
97
97
  packageVersion: boolean;
98
98
  };
99
99
 
100
- export type FixedEditorConfig = {
101
- enabled: boolean;
102
- mouseScroll: boolean;
103
- copyNotice: boolean;
104
- };
105
-
106
100
  export type PolishedEditorStyleConfig = {
107
101
  metadataFormat: string;
108
102
  };
@@ -198,10 +192,6 @@ export type ComponentsConfig = {
198
192
  footer: FooterComponentConfig;
199
193
  };
200
194
 
201
- export type LayoutConfig = {
202
- fixedEditor: FixedEditorConfig;
203
- };
204
-
205
195
  export type ExtensionStatusPlacement = "off" | "left" | "middle" | "right";
206
196
  export type ExtensionStatusColorMode = "zentui" | "original";
207
197
 
@@ -242,7 +232,6 @@ export type ZentuiConfig = {
242
232
  icons: ResolvedIcons;
243
233
  colors: PolishedTuiColors;
244
234
  components: ComponentsConfig;
245
- layout: LayoutConfig;
246
235
  };
247
236
 
248
237
  export type PolishedTuiColors = {
@@ -281,7 +270,7 @@ export type PolishedTuiColors = {
281
270
 
282
271
  /**
283
272
  * Canonical configuration plus a temporary flat compatibility projection used
284
- * by production consumers while they migrate to `components` and `layout`.
273
+ * by production consumers while they migrate to `components`.
285
274
  */
286
275
  export type PolishedTuiConfig = ZentuiConfig & {
287
276
  footerFormat: string;
@@ -304,7 +293,6 @@ export type PolishedTuiConfig = ZentuiConfig & {
304
293
  gitCommit: GitCommitConfig;
305
294
  gitMetrics: GitMetricsConfig;
306
295
  extensionStatuses: ExtensionStatusesConfig;
307
- fixedEditor: FixedEditorConfig;
308
296
  };
309
297
 
310
298
  /**
@@ -460,7 +448,6 @@ export const defaultConfig: PolishedTuiConfig = {
460
448
  os: "bold white",
461
449
  },
462
450
  components: defaultComponents,
463
- layout: { fixedEditor: { enabled: false, mouseScroll: true, copyNotice: true } },
464
451
  footerFormat: defaultStarshipStyle.format,
465
452
  responsiveFooter: defaultStarshipStyle.responsive,
466
453
  compactFooterFormat: defaultStarshipStyle.compactFormat,
@@ -485,7 +472,6 @@ export const defaultConfig: PolishedTuiConfig = {
485
472
  gitCommit: defaultStarshipStyle.gitCommit,
486
473
  gitMetrics: defaultStarshipStyle.gitMetrics,
487
474
  extensionStatuses: defaultStarshipStyle.extensionStatuses,
488
- fixedEditor: { enabled: false, mouseScroll: true, copyNotice: true },
489
475
  };
490
476
 
491
477
  type ConfigRecord = Record<string, unknown>;
@@ -731,21 +717,6 @@ function normalizeExtensionStatuses(record: Record<string, unknown>): ExtensionS
731
717
  };
732
718
  }
733
719
 
734
- function normalizeFixedEditorConfig(record: Record<string, unknown>): FixedEditorConfig {
735
- return {
736
- enabled:
737
- typeof record.enabled === "boolean" ? record.enabled : defaultConfig.fixedEditor.enabled,
738
- mouseScroll:
739
- typeof record.mouseScroll === "boolean"
740
- ? record.mouseScroll
741
- : defaultConfig.fixedEditor.mouseScroll,
742
- copyNotice:
743
- typeof record.copyNotice === "boolean"
744
- ? record.copyNotice
745
- : defaultConfig.fixedEditor.copyNotice,
746
- };
747
- }
748
-
749
720
  function isColorSourceKey(value: string): value is keyof ColorSourcesConfig {
750
721
  return value === "starship" || value === "editor" || value === "userMessages";
751
722
  }
@@ -1003,16 +974,6 @@ function resolveFooterSegments(canonical: unknown, legacy: unknown): FooterSegme
1003
974
  ) as FooterSegmentsConfig;
1004
975
  }
1005
976
 
1006
- function resolveFixedEditor(canonical: unknown, legacy: unknown): FixedEditorConfig {
1007
- const canonicalRecord = recordValue(canonical);
1008
- const legacyRecord = recordValue(legacy);
1009
- return normalizeFixedEditorConfig({
1010
- enabled: resolvedValue(canonicalRecord, "enabled", legacyRecord),
1011
- mouseScroll: resolvedValue(canonicalRecord, "mouseScroll", legacyRecord),
1012
- copyNotice: resolvedValue(canonicalRecord, "copyNotice", legacyRecord),
1013
- });
1014
- }
1015
-
1016
977
  function legacyCopyFriendly(record: ConfigRecord): boolean {
1017
978
  return record.copyFriendly === true;
1018
979
  }
@@ -1274,7 +1235,6 @@ function compatibilityView(config: ZentuiConfig): PolishedTuiConfig {
1274
1235
  statusLine: config.components.footer.style === "starship",
1275
1236
  viewportIndicators: config.components.editor.viewportIndicators,
1276
1237
  },
1277
- fixedEditor: config.layout.fixedEditor,
1278
1238
  };
1279
1239
  }
1280
1240
 
@@ -1316,7 +1276,6 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
1316
1276
  const config = isRecord(parsed) ? parsed : {};
1317
1277
  const iconsRecord = recordValue(config.icons);
1318
1278
  const colorsRecord = recordValue(config.colors);
1319
- const layout = recordValue(config.layout);
1320
1279
  const canonical: ZentuiConfig = {
1321
1280
  projectRefreshIntervalMs: parseProjectRefreshIntervalMs(config.projectRefreshIntervalMs),
1322
1281
  icons: resolveConfiguredIcons(
@@ -1325,9 +1284,6 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
1325
1284
  ),
1326
1285
  colors: { ...defaultConfig.colors, ...normalizeColors(colorsRecord) },
1327
1286
  components: resolveComponents(config),
1328
- layout: {
1329
- fixedEditor: resolveFixedEditor(layout.fixedEditor, config.fixedEditor),
1330
- },
1331
1287
  };
1332
1288
  const view = compatibilityView(canonical);
1333
1289
  const unsupported = new Set<ComponentStyleOwner>();
@@ -1639,21 +1595,6 @@ export function saveStarshipFooterStylePatch(
1639
1595
  );
1640
1596
  }
1641
1597
 
1642
- export function saveLayoutFixedEditorPatch(
1643
- patch: Partial<FixedEditorConfig>,
1644
- path = configPath,
1645
- ): PolishedTuiConfig {
1646
- return mutateConfig(path, (record) => {
1647
- const fixedEditor = mergeConfig(record).layout.fixedEditor;
1648
- if (patch.enabled !== undefined) fixedEditor.enabled = patch.enabled;
1649
- if (patch.mouseScroll !== undefined) fixedEditor.mouseScroll = patch.mouseScroll;
1650
- if (patch.copyNotice !== undefined) fixedEditor.copyNotice = patch.copyNotice;
1651
- const normalized = normalizeFixedEditorConfig(fixedEditor as unknown as ConfigRecord);
1652
- const layout = recordValue(record.layout);
1653
- record.layout = overlayKnown(layout, { fixedEditor: normalized });
1654
- });
1655
- }
1656
-
1657
1598
  export function saveColorSourcesPatch(
1658
1599
  patch: Partial<ColorSourcesConfig>,
1659
1600
  path = configPath,
@@ -1887,10 +1828,3 @@ export function saveExtensionStatusColorMode(
1887
1828
  });
1888
1829
  }, path);
1889
1830
  }
1890
-
1891
- export function saveFixedEditorPatch(
1892
- patch: Partial<FixedEditorConfig>,
1893
- path = configPath,
1894
- ): PolishedTuiConfig {
1895
- return saveLayoutFixedEditorPatch(patch, path);
1896
- }
@@ -13,7 +13,6 @@ import {
13
13
  type ExtensionStatusColorMode,
14
14
  type ExtensionStatusPlacement,
15
15
  ensureConfigExists,
16
- type FixedEditorConfig,
17
16
  FOOTER_FORMAT_ALIASES,
18
17
  type FooterComponentConfig,
19
18
  type FooterSegmentsConfig,
@@ -34,7 +33,6 @@ import {
34
33
  saveExtensionStatusPlacement,
35
34
  saveFooterComponentPatch,
36
35
  saveIconsModePatch,
37
- saveLayoutFixedEditorPatch,
38
36
  saveMinimalistEditorStylePatch,
39
37
  saveSelectorBordersComponentPatch,
40
38
  saveStarshipFooterStylePatch,
@@ -46,11 +44,6 @@ import {
46
44
  type EditorTransferFailureReason,
47
45
  replaceEditorComponentWithExpandedText,
48
46
  } from "./editor-transfer";
49
- import {
50
- disposeFixedEditor,
51
- installFixedEditorProbe,
52
- removeFixedEditorProbe,
53
- } from "./fixed-editor";
54
47
  import { installFooter, installHiddenFooter } from "./footer";
55
48
  import { collectFooterFormatReferences, parseFooterFormat } from "./footer-format";
56
49
  import { buildSessionDurationLabel, invalidateUsageTotalsCache } from "./format";
@@ -197,7 +190,6 @@ export default function (pi: ExtensionAPI) {
197
190
  let agentDurationMs: number | undefined;
198
191
  let minimalistProjectRoot: string | undefined;
199
192
  let projectRefreshActive = false;
200
- let fixedLayoutEnabled = false;
201
193
  let activeTuiContext: ExtensionContext | undefined;
202
194
 
203
195
  const isOwnedEditorFactory = (factory: EditorFactory | undefined) =>
@@ -891,35 +883,6 @@ export default function (pi: ExtensionAPI) {
891
883
  }
892
884
  };
893
885
 
894
- const cleanupFixedLayout = (ctx: ExtensionContext, reason: "live" | "shutdown" = "live") => {
895
- try {
896
- disposeFixedEditor(reason, ctx);
897
- } catch {
898
- // Best effort ordinary-layout fallback.
899
- }
900
- try {
901
- removeFixedEditorProbe(ctx);
902
- } catch {
903
- // Probe cleanup is independent from compositor disposal.
904
- }
905
- fixedLayoutEnabled = false;
906
- };
907
-
908
- const reconcileFixedLayout = (ctx: ExtensionContext) => {
909
- const enabled = currentConfig.layout.fixedEditor.enabled;
910
- if (enabled === fixedLayoutEnabled) return;
911
- if (!enabled) {
912
- cleanupFixedLayout(ctx);
913
- return;
914
- }
915
- try {
916
- installFixedEditorProbe(ctx, getCurrentConfig, sessionLifecycle);
917
- fixedLayoutEnabled = true;
918
- } catch {
919
- cleanupFixedLayout(ctx);
920
- }
921
- };
922
-
923
886
  const reconcileEditor = (
924
887
  ctx: ExtensionContext,
925
888
  options: { allowStaleZentui?: boolean } = {},
@@ -965,7 +928,6 @@ export default function (pi: ExtensionAPI) {
965
928
  reconcileUserMessages();
966
929
  reconcileSelectorBorders();
967
930
  reconcileFooter(ctx);
968
- reconcileFixedLayout(ctx);
969
931
  reconcileProjectRefresh(ctx);
970
932
  reconcileSessionTimer();
971
933
  reconcileAgentTimer();
@@ -983,7 +945,6 @@ export default function (pi: ExtensionAPI) {
983
945
  syncFooterState(ctx);
984
946
  stopProjectRefresh();
985
947
 
986
- cleanupFixedLayout(ctx);
987
948
  uninstallUserMessages();
988
949
  uninstallSelectorBorders();
989
950
  try {
@@ -1014,7 +975,11 @@ export default function (pi: ExtensionAPI) {
1014
975
  sessionLifecycle.defer(() => {
1015
976
  if (!isTuiContext(ctx) || !effectiveEditorEnabled()) return;
1016
977
  const observed = observeEditorFactory(ctx);
1017
- if (!observed.known || observed.factory === installedEditorFactory) return;
978
+ if (!observed.known) return;
979
+ if (observed.factory === installedEditorFactory) {
980
+ reconcileProjectRefresh(ctx);
981
+ return;
982
+ }
1018
983
  if (!observed.factory || !isOwnedEditorFactory(observed.factory)) {
1019
984
  clearEditorOwnership();
1020
985
  reconcileProjectRefresh(ctx);
@@ -1022,6 +987,7 @@ export default function (pi: ExtensionAPI) {
1022
987
  return;
1023
988
  }
1024
989
  trackZentuiEditorFactory(observed.factory);
990
+ reconcileProjectRefresh(ctx);
1025
991
  refresh();
1026
992
  });
1027
993
  };
@@ -1033,17 +999,6 @@ export default function (pi: ExtensionAPI) {
1033
999
  resetAgentTimer();
1034
1000
  stopProjectRefresh();
1035
1001
 
1036
- if (isTuiContext(ctx)) cleanupFixedLayout(ctx, "shutdown");
1037
- else {
1038
- try {
1039
- disposeFixedEditor("shutdown", ctx);
1040
- } catch {
1041
- // Continue cleaning independent surfaces.
1042
- } finally {
1043
- fixedLayoutEnabled = false;
1044
- }
1045
- }
1046
-
1047
1002
  let retainedEditorOwnership = false;
1048
1003
  if (isTuiContext(ctx)) {
1049
1004
  uninstallStatusLine(ctx, { forceLocalCleanup: true });
@@ -1107,7 +1062,7 @@ export default function (pi: ExtensionAPI) {
1107
1062
  setEditorComponent(patch: Partial<EditorComponentConfig>, ctx: ExtensionContext) {
1108
1063
  currentConfig = saveEditorComponentPatch(patch);
1109
1064
  let result: EditorChangeResult | undefined;
1110
- if ((patch.enabled !== undefined || patch.style !== undefined) && isTuiContext(ctx)) {
1065
+ if (patch.enabled !== undefined && isTuiContext(ctx)) {
1111
1066
  result = reconcileEditor(ctx);
1112
1067
  }
1113
1068
  if (patch.style !== undefined && patch.style !== "minimalist") {
@@ -1145,9 +1100,7 @@ export default function (pi: ExtensionAPI) {
1145
1100
  const previousStyle = effectiveFooterStyle();
1146
1101
  currentConfig = saveFooterComponentPatch(patch);
1147
1102
  const styleChanged = effectiveFooterStyle() !== previousStyle;
1148
- if (styleChanged && fixedLayoutEnabled) cleanupFixedLayout(ctx);
1149
1103
  if (patch.style !== undefined) reconcileFooter(ctx);
1150
- if (styleChanged && currentConfig.layout.fixedEditor.enabled) reconcileFixedLayout(ctx);
1151
1104
  if (patch.modelLabel !== undefined) syncFooterState(ctx);
1152
1105
  reconcileProjectRefresh(ctx, styleChanged);
1153
1106
  reconcileSessionTimer();
@@ -1212,11 +1165,6 @@ export default function (pi: ExtensionAPI) {
1212
1165
  setExtensionStatusColorMode(key: string, colorMode: ExtensionStatusColorMode) {
1213
1166
  currentConfig = saveExtensionStatusColorMode(key, colorMode);
1214
1167
  },
1215
- setFixedEditor(patch: Partial<FixedEditorConfig>, ctx: ExtensionContext) {
1216
- currentConfig = saveLayoutFixedEditorPatch(patch);
1217
- reconcileFixedLayout(ctx);
1218
- refresh();
1219
- },
1220
1168
  requestRender() {
1221
1169
  refresh();
1222
1170
  },
@@ -155,9 +155,12 @@ function renderTopRight(
155
155
  uiTheme: Theme,
156
156
  config: ZentuiConfig,
157
157
  availableWidth: number,
158
+ renderBorder: (text: string) => string,
158
159
  ): string {
159
160
  const source = config.components.editor.colorSource;
160
161
  const parts: string[] = [];
162
+ const joinParts = (values: string[]) =>
163
+ values.map((part, index) => (index > 0 ? `${renderBorder(" – ")}${part}` : part)).join("");
161
164
  const cost = config.components.editor.styles.minimalist.showCost
162
165
  ? sanitizeEditorMetadataText(metadata.costLabel ?? "")
163
166
  : "";
@@ -213,7 +216,7 @@ function renderTopRight(
213
216
  for (const gaugeWidth of [5, 3]) {
214
217
  const gauge = `[${buildContextGauge(percent, gaugeWidth, config.icons.mode === "ascii")}] ${text}`;
215
218
  const styledGauge = renderStyleForSource(uiTheme, source, style, gauge);
216
- const candidate = joinStyled([...parts, styledGauge], safeThemeFg(uiTheme, "muted", " – "));
219
+ const candidate = joinParts([...parts, styledGauge]);
217
220
  if (visibleWidth(candidate) <= availableWidth) {
218
221
  context = styledGauge;
219
222
  break;
@@ -222,7 +225,7 @@ function renderTopRight(
222
225
  }
223
226
  parts.push(context);
224
227
  }
225
- return joinStyled(parts, safeThemeFg(uiTheme, "muted", " – "));
228
+ return joinParts(parts);
226
229
  }
227
230
 
228
231
  function renderBottomLeft(
@@ -389,7 +392,7 @@ export function renderMinimalistFrame({
389
392
  width,
390
393
  left: topLeft,
391
394
  leftFallbacks: topFallbacks,
392
- right: renderTopRight(metadata, uiTheme, config, topRightBudget),
395
+ right: renderTopRight(metadata, uiTheme, config, topRightBudget, renderBorder),
393
396
  leftCorner: "╭",
394
397
  rightCorner: "╮",
395
398
  renderBorder,