pi-one-ui 0.4.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -6,6 +6,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.0] - 2026-09-05
10
+
11
+ ### Changed
12
+
13
+ - Reduced the Editor to the single `minimalist` style with an `on`/`off` switch: `off` restores Pi's native editor, and the retired `opencode` style is removed (`enabled` merges into `style`, legacy values migrate automatically).
14
+ - Added `/oneui` panel settings for the Editor component: style (`on`/`off`), color source (`theme`/`terminal`), and border color mode (`static`/`adaptive`), each with an inline description.
15
+ - In `off` mode, an explicitly configured `colors.editorBorder` is applied to the native editor border through `colorSource` (theme tokens or fixed terminal colors); without it the native effort/theme coloring is preserved.
16
+
17
+ ### Performance
18
+
19
+ - Cached `ToolExecutionComponent` render widgets across expand/collapse toggles: repeated Ctrl+O toggles and identical result updates now skip the full native rebuild, cutting batch expansion of many tools (e.g. 30 × 1000-line outputs) from ~130ms to under 0.2ms while keeping expanded and collapsed render slots independent.
20
+
21
+ ### Removed
22
+
23
+ - Removed the `opencode` Editor style along with its polished renderer, completion-menu module, and `styles.opencode` configuration; the `enabled` field is gone.
24
+
25
+ ### Migration
26
+
27
+ - `components.editor.enabled: false` becomes `components.editor.style: "off"`; `style: "opencode"`/`"minimalist"` become `"on"`. The `styles.opencode` block and `opencode-copy-friendly`/`accent-rail` selections are ignored.
28
+
29
+ ### Fixed
30
+
31
+ - Toggling the Editor style between `on`/`off` no longer replaces the editor factory: the existing instance (and Pi's overlay focus target) stays valid, so the `/oneui` panel restores keyboard focus to the input editor after closing.
32
+ - Unconfigured `cwd`, model-label, and static border colors now prefer the theme's `cwd`/`editorModel`/`editorBorder` tokens (which may reference `vars` variables or hex), falling back to Pi's native defaults when the theme does not define them.
33
+
9
34
  ## [0.4.0] - 2026-09-03
10
35
 
11
36
  ### Changed
@@ -62,7 +87,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
62
87
 
63
88
  - Reduced rendering lag when expanding settled tool groups by reusing cached child output.
64
89
 
65
- [Unreleased]: https://github.com/kerolt/pi-one-ui/compare/v0.4.0...HEAD
90
+ [Unreleased]: https://github.com/kerolt/pi-one-ui/compare/v0.5.0...HEAD
91
+ [0.5.0]: https://github.com/kerolt/pi-one-ui/compare/v0.4.0...v0.5.0
66
92
  [0.4.0]: https://github.com/kerolt/pi-one-ui/compare/v0.3.1...v0.4.0
67
93
  [0.3.1]: https://github.com/kerolt/pi-one-ui/compare/v0.3.0...v0.3.1
68
94
  [0.3.0]: https://github.com/kerolt/pi-one-ui/compare/v0.2.2...v0.3.0
package/README.en.md CHANGED
@@ -26,7 +26,7 @@ Header → Context → WorkingLine → Editor → Footer
26
26
  - **Header**: startup information, logo, and shortcut hints.
27
27
  - **Context**: the conversation area, including user messages, assistant messages, thinking, tools, diffs, Markdown, and summaries.
28
28
  - **WorkingLine**: working state, spinner, token/thought/elapsed information, live output rate, and turn summaries.
29
- - **Editor**: input editor, completion, metadata, and the Opencode and Minimalist styles.
29
+ - **Editor**: input editor, completion, metadata, and the Minimalist style (Pi native is one toggle away).
30
30
  - **Footer**: current directory, Git, runtime, token, cost, and extension status information.
31
31
  - **Overlay**: temporary interfaces such as the settings panel and Context Inspector, managed by a shared OverlayManager.
32
32
 
@@ -90,8 +90,9 @@ Using the `/oneui` settings panel is recommended. The panel uses a top-centered
90
90
  "version": 1,
91
91
  "components": {
92
92
  "editor": {
93
- "enabled": true,
94
- "style": "opencode"
93
+ "style": "on",
94
+ "colorSource": "theme",
95
+ "borderColorMode": "static"
95
96
  },
96
97
  "userMessages": {
97
98
  "enabled": true,
@@ -111,7 +112,7 @@ Using the `/oneui` settings panel is recommended. The panel uses a top-centered
111
112
  }
112
113
  ```
113
114
 
114
- The Editor now supports only the `opencode` and `minimalist` styles. `minimalist` no longer duplicates context-usage information; context percentage, token totals, thresholds, gauges, and related presentation are configured through Footer. Existing configurations that select the removed `opencode-copy-friendly` or `accent-rail` styles safely fall back to the default `opencode`, and their old nested style settings are ignored.
115
+ The Editor keeps a single `minimalist` decoration style controlled by `style`: `on` enables the Minimalist decoration, `off` restores Pi's native editor (the border follows the theme and effort coloring by default; when `colors.editorBorder` is explicitly configured, the off mode applies that color through `colorSource`, overriding the native effort coloring). Legacy configurations migrate automatically: `enabled: false` becomes `style: "off"`, and `opencode`/`minimalist` become `style: "on"`; the `styles.opencode` block and the retired `opencode-copy-friendly`/`accent-rail` styles are ignored. `borderColorMode` supports `static` (fixed `colors.editorBorder`) and `adaptive` (border shifts with the effort level); `colorSource` supports `theme` (colors resolved through the active Pi theme tokens) and `terminal` (fixed terminal colors). When `cwd`, the model label, or the static border are not explicitly configured, the theme's `cwd`/`editorModel`/`editorBorder` tokens are used first (they may point to `vars` variables or hex values), falling back to Pi's native defaults when the theme does not define them; the terminal source falls back to Pi's native colors as well when unconfigured (only an explicit terminal color name or hex is rendered fixed). Context-usage information is presented by Footer.
115
116
 
116
117
  The WorkingLine token segment appends live output throughput, such as `⚡12 tok/s`, after a model response has run for at least 500ms. Throughput is calculated independently for the current response and resets on the next `turn_start`; disabling the token segment hides it as well.
117
118
 
package/README.md CHANGED
@@ -26,7 +26,7 @@ Header → Context → WorkingLine → Editor → Footer
26
26
  - **Header**:启动信息、Logo 和快捷键提示。
27
27
  - **Context**:对话内容区,包含用户消息、Assistant 消息、Thinking、Tool、Diff、Markdown 和 Summary。
28
28
  - **WorkingLine**:工作状态、spinner、token/thought/elapsed、实时输出速率和回合摘要。
29
- - **Editor**:输入编辑器、completion、metadata,以及 Opencode 和 Minimalist 样式。
29
+ - **Editor**:输入编辑器、completion、metadata,以及 Minimalist 样式(可切换 Pi 原生)。
30
30
  - **Footer**:目录、Git、runtime、token、cost 和扩展状态等信息。
31
31
  - **Overlay**:设置面板、Context Inspector 等临时浮层由统一的 OverlayManager 管理。
32
32
 
@@ -83,15 +83,17 @@ pi install git:github.com/kerolt/pi-one-ui
83
83
  ~/.pi/agent/pi-one-ui.json
84
84
  ```
85
85
 
86
- 推荐通过 `/oneui` 设置面板修改配置。面板采用更靠近顶部的居中布局;切换 Editor 启用状态或样式时会保持打开并在 Editor 替换后恢复焦点,持久化失败时则恢复列表中的有效旧值。当前配置仍使用 v1 结构,例如:
86
+
87
+ 推荐通过 `/oneui` 设置面板修改配置。面板采用更靠近顶部的居中布局;切换 Editor 开关、颜色源或边框模式时会保持打开并在 Editor 替换后恢复焦点,持久化失败时则恢复列表中的有效旧值。当前配置仍使用 v1 结构,例如:
87
88
 
88
89
  ```json
89
90
  {
90
91
  "version": 1,
91
92
  "components": {
92
93
  "editor": {
93
- "enabled": true,
94
- "style": "opencode"
94
+ "style": "on",
95
+ "colorSource": "theme",
96
+ "borderColorMode": "static"
95
97
  },
96
98
  "userMessages": {
97
99
  "enabled": true,
@@ -111,7 +113,7 @@ pi install git:github.com/kerolt/pi-one-ui
111
113
  }
112
114
  ```
113
115
 
114
- Editor 当前只支持 `opencode` 和 `minimalist` 两种样式。`minimalist` 不再重复显示上下文占用信息;上下文百分比、token 总量、阈值和 gauge 等展示统一由 Footer 配置。已有配置若选择了已移除的 `opencode-copy-friendly` 或 `accent-rail`,运行时会安全回退到默认的 `opencode`,相应的旧嵌套样式配置会被忽略。
116
+ Editor 只保留 `minimalist` 一种装饰样式,通过 `style` 开关控制:`on` 启用 Minimalist 装饰,`off` 恢复 Pi 原生编辑器(边框默认跟随主题与 effort 变色;若显式配置了 `colors.editorBorder`,off 模式下也会按 `colorSource` 应用该颜色,覆盖原生 effort 变色)。旧配置的 `enabled: false` 会迁移为 `style: "off"`,`opencode`/`minimalist` 会迁移为 `style: "on"`,`styles.opencode` 嵌套配置与 `opencode-copy-friendly`、`accent-rail` 一并失效。`borderColorMode` 支持 `static`(固定 `colors.editorBorder`)与 `adaptive`(边框随 effort 档位变化);`colorSource` 支持 `theme`(颜色取当前主题 token)与 `terminal`(固定终端色)。cwd、模型名与边框(static)未显式配置时优先使用主题的 `cwd`/`editorModel`/`editorBorder` token(可指向 vars 变量或 hex),主题未定义则回落 Pi 原生默认;terminal 源未配置时同样回落 Pi 原生颜色(配置了终端色名/hex 才固定渲染)。上下文占用相关信息由 Footer 统一展示。
115
117
 
116
118
  WorkingLine 的 token segment 会在一次模型响应持续至少 500ms 后追加实时输出速率,例如 `⚡12 tok/s`。速率按当前响应独立计算,在下一次 `turn_start` 时重置;关闭 token segment 时也会一并隐藏。
117
119
 
@@ -3,7 +3,6 @@ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
3
  import { renderUserMessageStyle } from "../../layouts/context/message/user-message-styles.ts";
4
4
  import { sanitizeEditorMetadataText } from "../../layouts/editor/editor-metadata-format.ts";
5
5
  import { renderMinimalistFrame } from "../../layouts/editor/minimalist-editor.ts";
6
- import { renderPolishedEditorFrame } from "../../layouts/editor/ui.ts";
7
6
  import { safeThemeFg } from "../../shared/style.ts";
8
7
  import type { PolishedTuiConfig } from "../config/shell.ts";
9
8
 
@@ -59,50 +58,30 @@ export function renderEditorSettingsPreview(
59
58
  const viewport = editor.viewportIndicators
60
59
  ? { above: "2", below: "3" }
61
60
  : undefined;
62
- let frame: string[];
63
- if (editor.style === "minimalist") {
64
- frame = renderMinimalistFrame({
65
- width: previewWidth,
66
- editorLines,
67
- viewport,
68
- inputText: EDITOR_PREVIEW_INPUT,
69
- metadata: {
70
- cwd: "/workspace/zentui/src",
71
- projectRoot: "/workspace/zentui",
72
- branch: "feat/settings-previews",
73
- dirty: true,
74
- ahead: 2,
75
- behind: 1,
76
- costLabel: "$.12",
77
- modelLabel,
78
- thinkingLevel: "high",
79
- sessionName: "Preview",
80
- agentDurationMs: 12_000,
81
- agentActive: true,
82
- },
83
- uiTheme: theme,
84
- config: safeConfig,
85
- borderColor,
86
- });
87
- } else {
88
- frame = renderPolishedEditorFrame({
89
- width: previewWidth,
90
- editorLines,
91
- autocompleteLines,
92
- viewport,
93
- uiTheme: theme,
94
- config: safeConfig,
95
- modelMeta: {
96
- modelLabel,
97
- modelId: "sonnet-4",
98
- modelName: "Sonnet 4",
99
- providerLabel: "Anthropic",
100
- sessionName: "Preview",
101
- },
61
+ const frame = renderMinimalistFrame({
62
+ width: previewWidth,
63
+ editorLines,
64
+ autocompleteLines,
65
+ viewport,
66
+ inputText: EDITOR_PREVIEW_INPUT,
67
+ metadata: {
68
+ cwd: "/workspace/zentui/src",
69
+ projectRoot: "/workspace/zentui",
70
+ branch: "feat/settings-previews",
71
+ dirty: true,
72
+ ahead: 2,
73
+ behind: 1,
74
+ costLabel: "$.12",
75
+ modelLabel,
102
76
  thinkingLevel: "high",
103
- borderColor,
104
- });
105
- }
77
+ sessionName: "Preview",
78
+ agentDurationMs: 12_000,
79
+ agentActive: true,
80
+ },
81
+ uiTheme: theme,
82
+ config: safeConfig,
83
+ borderColor,
84
+ });
106
85
  return boundedRows(frame, previewWidth);
107
86
  }
108
87
 
@@ -24,7 +24,7 @@ export type { IconMode } from "../../shared/icons.ts";
24
24
  export type ContextStyle = "text" | "gauge" | "text+gauge";
25
25
  export type SeparatorStyle = "pipe" | "dot" | "chevron" | "none";
26
26
  export type ModelLabelSource = "id" | "name";
27
- export type EditorStyle = "opencode" | "minimalist";
27
+ export type EditorStyle = "on" | "off";
28
28
  export type UserMessageStyle =
29
29
  | "framed"
30
30
  | "framed-copy-friendly"
@@ -46,7 +46,6 @@ export type ComponentStyleOwner =
46
46
  | "footer";
47
47
  export type MinimalistPathDisplayMode = "compact" | "project" | "full";
48
48
  export type EditorBorderColorMode = "static" | "adaptive";
49
- export type CompletionMenuStyle = "native" | "palette";
50
49
  export type CompactFooterMaxLines = 1 | 2 | 3 | "unlimited";
51
50
 
52
51
  export const DEFAULT_COMPACT_FOOTER_FORMAT =
@@ -103,11 +102,6 @@ export type FooterSegmentsConfig = {
103
102
  packageVersion: boolean;
104
103
  };
105
104
 
106
- export type PolishedEditorStyleConfig = {
107
- metadataFormat: string;
108
- completionMenu: CompletionMenuStyle;
109
- };
110
-
111
105
  export type MinimalistEditorStyleConfig = {
112
106
  pathDisplay: MinimalistPathDisplayMode;
113
107
  showSessionName: boolean;
@@ -124,14 +118,12 @@ export type EditorStylesConfig = {
124
118
  };
125
119
 
126
120
  export type EditorComponentConfig = {
127
- enabled: boolean;
128
121
  style: EditorStyle;
129
122
  colorSource: ColorSource;
130
123
  borderColorMode: EditorBorderColorMode;
131
124
  modelLabel: ModelLabelSource;
132
125
  viewportIndicators: boolean;
133
126
  styles: {
134
- opencode: PolishedEditorStyleConfig;
135
127
  minimalist: MinimalistEditorStyleConfig;
136
128
  };
137
129
  };
@@ -281,7 +273,7 @@ export type ZentuiConfig = {
281
273
  };
282
274
 
283
275
  export type PolishedTuiColors = {
284
- cwd: ColorSpec;
276
+ cwd?: ColorSpec;
285
277
  sessionName: ColorSpec;
286
278
  gitBranch: ColorSpec;
287
279
  gitStatus: ColorSpec;
@@ -414,8 +406,6 @@ const defaultFooterSegments: FooterSegmentsConfig = {
414
406
  packageVersion: false,
415
407
  };
416
408
 
417
- const DEFAULT_COMPLETION_MENU: CompletionMenuStyle = "palette";
418
-
419
409
  const defaultMinimalistStyle: MinimalistEditorStyleConfig = {
420
410
  pathDisplay: "compact",
421
411
  showSessionName: true,
@@ -446,17 +436,12 @@ const defaultStarshipStyle: StarshipFooterStyleConfig = {
446
436
 
447
437
  const defaultComponents: ComponentsConfig = {
448
438
  editor: {
449
- enabled: true,
450
- style: "opencode",
439
+ style: "on",
451
440
  colorSource: "theme",
452
441
  borderColorMode: "static",
453
442
  modelLabel: "id",
454
443
  viewportIndicators: true,
455
444
  styles: {
456
- opencode: {
457
- metadataFormat: DEFAULT_EDITOR_METADATA_FORMAT,
458
- completionMenu: DEFAULT_COMPLETION_MENU,
459
- },
460
445
  minimalist: defaultMinimalistStyle,
461
446
  },
462
447
  },
@@ -496,7 +481,6 @@ export const defaultConfig: PolishedTuiConfig = {
496
481
  projectRefreshIntervalMs: DEFAULT_PROJECT_REFRESH_INTERVAL_MS,
497
482
  icons: { mode: "auto", ...NERD_DEFAULT_ICONS },
498
483
  colors: {
499
- cwd: "bold cyan",
500
484
  sessionName: "bold green",
501
485
  gitBranch: "bold purple",
502
486
  gitStatus: "bold red",
@@ -578,7 +562,9 @@ function parseEditorModelLabel(
578
562
  }
579
563
 
580
564
  function parseEditorStyle(value: unknown): EditorStyle {
581
- if (value === "opencode" || value === "minimalist") return value;
565
+ if (value === "on" || value === "off") return value;
566
+ // 迁移:旧 opencode / minimalist 样式统一收敛为 on(minimalist)。
567
+ if (value === "opencode" || value === "minimalist") return "on";
582
568
  return defaultComponents.editor.style;
583
569
  }
584
570
 
@@ -587,12 +573,6 @@ function parseEditorBorderColorMode(value: unknown): EditorBorderColorMode {
587
573
  return defaultConfig.editorBorderColorMode;
588
574
  }
589
575
 
590
- function parseCompletionMenuStyle(value: unknown): CompletionMenuStyle {
591
- return value === "native" || value === "palette"
592
- ? value
593
- : DEFAULT_COMPLETION_MENU;
594
- }
595
-
596
576
  export function isSeparatorStyle(value: unknown): value is SeparatorStyle {
597
577
  return (
598
578
  value === "pipe" ||
@@ -1027,7 +1007,6 @@ function resolveComponents(config: ConfigRecord): ComponentsConfig {
1027
1007
  const components = recordValue(config.components);
1028
1008
  const editor = recordValue(components.editor);
1029
1009
  const editorStyles = recordValue(editor.styles);
1030
- const opencode = recordValue(editorStyles.opencode);
1031
1010
  const minimalist = recordValue(editorStyles.minimalist);
1032
1011
  const userMessages = recordValue(components.userMessages);
1033
1012
  const workingLine = recordValue(components.workingLine);
@@ -1040,8 +1019,12 @@ function resolveComponents(config: ConfigRecord): ComponentsConfig {
1040
1019
 
1041
1020
  return {
1042
1021
  editor: {
1043
- enabled: parseBoolean(editor.enabled, defaultComponents.editor.enabled),
1044
- style: parseEditorStyle(editor.style),
1022
+ // 迁移:旧 enabled:false 收敛为 style:"off"(enabled 字段已移除)。
1023
+ style:
1024
+ hasOwn(editor, "enabled") &&
1025
+ parseBoolean(editor.enabled, true) === false
1026
+ ? "off"
1027
+ : parseEditorStyle(editor.style),
1045
1028
  colorSource: parseColorSource(
1046
1029
  editor.colorSource,
1047
1030
  defaultComponents.editor.colorSource,
@@ -1056,13 +1039,6 @@ function resolveComponents(config: ConfigRecord): ComponentsConfig {
1056
1039
  defaultComponents.editor.viewportIndicators,
1057
1040
  ),
1058
1041
  styles: {
1059
- opencode: {
1060
- metadataFormat: parseNonEmptyString(
1061
- opencode.metadataFormat,
1062
- DEFAULT_EDITOR_METADATA_FORMAT,
1063
- ),
1064
- completionMenu: parseCompletionMenuStyle(opencode.completionMenu),
1065
- },
1066
1042
  minimalist: {
1067
1043
  pathDisplay:
1068
1044
  minimalist.pathDisplay === "compact" ||
@@ -1241,8 +1217,7 @@ function derivedRuntimeView(config: ZentuiConfig): PolishedTuiConfig {
1241
1217
  extensionStatuses: starship.extensionStatuses,
1242
1218
  editorStyle: config.components.editor.style,
1243
1219
  editorStyles: { minimalist },
1244
- editorMetadataFormat:
1245
- config.components.editor.styles.opencode.metadataFormat,
1220
+ editorMetadataFormat: DEFAULT_EDITOR_METADATA_FORMAT,
1246
1221
  editorBorderColorMode: config.components.editor.borderColorMode,
1247
1222
  editorModelLabel: config.components.editor.modelLabel,
1248
1223
  colorSources: {
@@ -1251,7 +1226,7 @@ function derivedRuntimeView(config: ZentuiConfig): PolishedTuiConfig {
1251
1226
  userMessages: config.components.userMessages.colorSource,
1252
1227
  },
1253
1228
  features: {
1254
- editor: config.components.editor.enabled,
1229
+ editor: config.components.editor.style === "on",
1255
1230
  statusLine: config.components.footer.style === "starship",
1256
1231
  viewportIndicators: config.components.editor.viewportIndicators,
1257
1232
  },
@@ -1263,8 +1238,10 @@ const unsupportedComponentStyles = new WeakMap<
1263
1238
  ReadonlySet<ComponentStyleOwner>
1264
1239
  >();
1265
1240
 
1266
- // Retired selections migrate to Opencode instead of disabling the custom Editor.
1241
+ // Retired selections migrate to the sole "on" (minimalist) style instead of
1242
+ // disabling the custom Editor. opencode itself is retired along with its variants.
1267
1243
  const retiredEditorStyleIds: ReadonlySet<string> = new Set([
1244
+ "opencode",
1268
1245
  "opencode-copy-friendly",
1269
1246
  "accent-rail",
1270
1247
  ]);
@@ -1273,7 +1250,7 @@ const knownComponentStyleIds: Record<
1273
1250
  ComponentStyleOwner,
1274
1251
  ReadonlySet<string>
1275
1252
  > = {
1276
- editor: new Set(["opencode", "minimalist"]),
1253
+ editor: new Set(["on", "off"]),
1277
1254
  userMessages: new Set([
1278
1255
  "framed",
1279
1256
  "framed-copy-friendly",
@@ -1441,7 +1418,6 @@ function applyEditorComponentPatch(
1441
1418
  patch: Partial<
1442
1419
  Pick<
1443
1420
  EditorComponentConfig,
1444
- | "enabled"
1445
1421
  | "style"
1446
1422
  | "colorSource"
1447
1423
  | "borderColorMode"
@@ -1450,7 +1426,6 @@ function applyEditorComponentPatch(
1450
1426
  >
1451
1427
  >,
1452
1428
  ): void {
1453
- if (patch.enabled !== undefined) component.enabled = patch.enabled;
1454
1429
  if (patch.style !== undefined) component.style = patch.style;
1455
1430
  if (patch.colorSource !== undefined)
1456
1431
  component.colorSource = patch.colorSource;
@@ -1466,7 +1441,6 @@ export function saveEditorComponentPatch(
1466
1441
  patch: Partial<
1467
1442
  Pick<
1468
1443
  EditorComponentConfig,
1469
- | "enabled"
1470
1444
  | "style"
1471
1445
  | "colorSource"
1472
1446
  | "borderColorMode"
@@ -1483,19 +1457,6 @@ export function saveEditorComponentPatch(
1483
1457
  );
1484
1458
  }
1485
1459
 
1486
- export function savePolishedEditorStylePatch(
1487
- patch: Partial<PolishedEditorStyleConfig>,
1488
- path = configPath,
1489
- ): PolishedTuiConfig {
1490
- return saveComponentsMutation((components) => {
1491
- const style = components.editor.styles.opencode;
1492
- if (patch.metadataFormat !== undefined)
1493
- style.metadataFormat = patch.metadataFormat;
1494
- if (patch.completionMenu !== undefined)
1495
- style.completionMenu = patch.completionMenu;
1496
- }, path);
1497
- }
1498
-
1499
1460
  function applyMinimalistStylePatch(
1500
1461
  style: MinimalistEditorStyleConfig,
1501
1462
  patch: Partial<MinimalistEditorStyleConfig>,
@@ -1695,7 +1656,7 @@ export function saveUiFeaturesPatch(
1695
1656
  return saveComponentsMutation(
1696
1657
  (components) => {
1697
1658
  if (valid.editor !== undefined) {
1698
- components.editor.enabled = valid.editor;
1659
+ components.editor.style = valid.editor ? "on" : "off";
1699
1660
  components.userMessages.enabled = valid.editor;
1700
1661
  components.selectorBorders.enabled = valid.editor;
1701
1662
  }
@@ -23,10 +23,11 @@ import {
23
23
  } from "./config/renderer.ts";
24
24
  import {
25
25
  type EditorComponentConfig,
26
- type EditorStyle,
27
26
  type FooterComponentConfig,
28
27
  type FooterStyle,
29
28
  loadConfig as loadShellConfig,
29
+ saveColorSourcesPatch,
30
+ saveEditorBorderColorMode,
30
31
  saveEditorComponentPatch,
31
32
  saveFooterComponentPatch,
32
33
  saveUserMessagesComponentPatch,
@@ -40,7 +41,6 @@ import { overlayManager } from "./overlay/overlay-manager.ts";
40
41
  import { ownerFor } from "./ownership.ts";
41
42
  import { applyPreset, PRESET_VALUES, type Preset } from "./presets.ts";
42
43
 
43
- const EDITOR_STYLES: EditorStyle[] = ["opencode", "minimalist"];
44
44
  const MESSAGE_STYLES: UserMessageStyle[] = [
45
45
  "framed",
46
46
  "framed-copy-friendly",
@@ -126,18 +126,28 @@ function editorItems(): SettingItem[] {
126
126
  const config = loadShellConfig();
127
127
  return [
128
128
  {
129
- id: "editorEnabled",
129
+ id: "editorStyle",
130
130
  label: "Editor",
131
- description: "Enable the custom input editor.",
132
- currentValue: onOff(config.components.editor.enabled),
131
+ description:
132
+ "on applies the Minimalist decoration; off restores Pi's native editor.",
133
+ currentValue: config.components.editor.style,
133
134
  values: ["on", "off"],
134
135
  },
135
136
  {
136
- id: "editorStyle",
137
- label: "Editor style",
138
- description: "Choose the input editor treatment.",
139
- currentValue: config.components.editor.style,
140
- values: EDITOR_STYLES,
137
+ id: "editorColorSource",
138
+ label: "Color source",
139
+ description:
140
+ "theme resolves colors through the active Pi theme; terminal uses fixed terminal colors.",
141
+ currentValue: config.components.editor.colorSource,
142
+ values: ["theme", "terminal"],
143
+ },
144
+ {
145
+ id: "editorBorderColorMode",
146
+ label: "Border color",
147
+ description:
148
+ "static keeps one border color; adaptive shifts the border with the effort level.",
149
+ currentValue: config.components.editor.borderColorMode,
150
+ values: ["static", "adaptive"],
141
151
  },
142
152
  ];
143
153
  }
@@ -429,18 +439,31 @@ function updateSetting(
429
439
  ctx: any,
430
440
  deps: UnifiedPanelDeps,
431
441
  ): void {
432
- if (id === "editorEnabled") {
433
- if (deps.runtime)
434
- deps.runtime.setEditorComponent({ enabled: isOn(value) }, ctx);
435
- else saveEditorComponentPatch({ enabled: isOn(value) });
442
+ if (id === "editorStyle" && (value === "on" || value === "off")) {
443
+ if (deps.runtime) deps.runtime.setEditorComponent({ style: value }, ctx);
444
+ else saveEditorComponentPatch({ style: value });
436
445
  saveNotice(ctx, "Editor", value);
437
446
  return;
438
447
  }
439
- if (id === "editorStyle" && EDITOR_STYLES.includes(value as EditorStyle)) {
448
+ if (
449
+ id === "editorColorSource" &&
450
+ (value === "theme" || value === "terminal")
451
+ ) {
440
452
  if (deps.runtime)
441
- deps.runtime.setEditorComponent({ style: value as EditorStyle }, ctx);
442
- else saveEditorComponentPatch({ style: value as EditorStyle });
443
- saveNotice(ctx, "Editor style", value);
453
+ deps.runtime.setEditorComponent({ colorSource: value }, ctx);
454
+ else saveColorSourcesPatch({ editor: value });
455
+ saveNotice(ctx, "Color source", value);
456
+ return;
457
+ }
458
+ if (id === "editorBorderColorMode") {
459
+ const valid =
460
+ value === "static" || value === "adaptive" ? value : undefined;
461
+ if (valid) {
462
+ if (deps.runtime)
463
+ deps.runtime.setEditorComponent({ borderColorMode: valid }, ctx);
464
+ else saveEditorBorderColorMode(valid);
465
+ saveNotice(ctx, "Border color", value);
466
+ }
444
467
  return;
445
468
  }
446
469
  if (id === "userMessagesEnabled") {
@@ -615,7 +638,7 @@ export async function showOneUiPanel(
615
638
  createList();
616
639
  list.selectItem(id);
617
640
  } finally {
618
- if (id === "editorEnabled" || id === "editorStyle") {
641
+ if (id === "editorStyle") {
619
642
  panelHandle?.focus();
620
643
  }
621
644
  tui.requestRender();
@@ -17,7 +17,7 @@ export function isPreset(value: string): value is Preset {
17
17
  /** Apply only ownership-related values; detailed options remain user-editable. */
18
18
  export function applyPreset(preset: Preset): void {
19
19
  if (preset === "native") {
20
- saveEditorComponentPatch({ enabled: false });
20
+ saveEditorComponentPatch({ style: "off" });
21
21
  saveUserMessagesComponentPatch({ enabled: false });
22
22
  saveWorkingLineComponentPatch({ enabled: false });
23
23
  saveFooterComponentPatch({ style: "native" });
@@ -25,7 +25,7 @@ export function applyPreset(preset: Preset): void {
25
25
  return;
26
26
  }
27
27
 
28
- saveEditorComponentPatch({ enabled: true });
28
+ saveEditorComponentPatch({ style: "on" });
29
29
  saveUserMessagesComponentPatch({ enabled: true });
30
30
  saveWorkingLineComponentPatch({ enabled: true });
31
31
  saveFooterComponentPatch({ style: "starship" });
@@ -47,6 +47,10 @@ import {
47
47
  setMessageDisplayTheme,
48
48
  } from "./tool/message-display.ts";
49
49
  import { clearAllAnimations } from "./tool/result.ts";
50
+ import {
51
+ installToggleRenderCache,
52
+ type ToggleRenderCacheHooks,
53
+ } from "./tool/toggle-render-cache.ts";
50
54
 
51
55
  /**
52
56
  * Claude Code Style for pi — 装配入口。
@@ -143,6 +147,7 @@ export default function (
143
147
  | {
144
148
  defaultMode: DefaultModeHooks;
145
149
  toolGrouping: ToolGroupingHooks;
150
+ toggleRenderCache: ToggleRenderCacheHooks;
146
151
  compactMode: CompactModeHooks;
147
152
  disposeMessageDisplay: () => void;
148
153
  disposeToolExpandedBackground: () => void;
@@ -162,12 +167,15 @@ export default function (
162
167
  });
163
168
  compactModeHooks = compactMode;
164
169
  const disposeMessageDisplay = installMessageDisplayRendering();
170
+ // 跨 toggle 重建缓存必须在展开背景 patch 之下(背景后装外层,shutdown 先拆)。
171
+ const toggleRenderCache = installToggleRenderCache();
165
172
  // 展开背景必须在 compact-mode 之后装,shutdown 时先于 compact 释放。
166
173
  const disposeToolExpandedBackground = installToolExpandedBackground();
167
174
  deactivateLegacyCompactionRendering();
168
175
  installation = {
169
176
  defaultMode,
170
177
  toolGrouping,
178
+ toggleRenderCache,
171
179
  compactMode,
172
180
  disposeMessageDisplay,
173
181
  disposeToolExpandedBackground,
@@ -180,6 +188,9 @@ export default function (
180
188
  applyStyleMode(mode, ctx, installation?.toolGrouping),
181
189
  updateConfig: (partial, ctx) => {
182
190
  updateConfig(partial);
191
+ // 配置(如 expandedPreviewMaxLines / excludeRenderers)变化可能改变
192
+ // 渲染结果:丢弃工具卡跨 toggle 缓存,下次 updateDisplay 重建。
193
+ installation?.toggleRenderCache.clear();
183
194
  refreshCurrentContext(ctx, installation?.toolGrouping);
184
195
  },
185
196
  });
@@ -213,6 +224,7 @@ export default function (
213
224
  installToolMouseInteraction(ctx, mouseOwner);
214
225
  if (!hooks) return;
215
226
  hooks.toolGrouping.setTheme(ctx.ui.theme);
227
+ hooks.toggleRenderCache.setTheme(ctx.ui.theme);
216
228
  setMessageDisplayTheme(ctx.ui.theme);
217
229
  ctx.ui.setStatus("ccstyle", undefined);
218
230
  // Collect the resumed context before syncing compact patches and expansion state.
@@ -231,6 +243,7 @@ export default function (
231
243
  installToolMouseInteraction(ctx, mouseOwner);
232
244
  if (!hooks) return;
233
245
  hooks.toolGrouping.setTheme(ctx.ui.theme);
246
+ hooks.toggleRenderCache.setTheme(ctx.ui.theme);
234
247
  setMessageDisplayTheme(ctx.ui.theme);
235
248
  syncCompactMode(ctx);
236
249
  scheduleSessionRender(() => {
@@ -248,6 +261,7 @@ export default function (
248
261
 
249
262
  pi.on("tool_execution_start", async (_event, ctx) => {
250
263
  installation?.toolGrouping.setTheme(ctx.ui.theme);
264
+ installation?.toggleRenderCache.setTheme(ctx.ui.theme);
251
265
  });
252
266
 
253
267
  pi.on("session_shutdown", async () => {
@@ -260,6 +274,7 @@ export default function (
260
274
  current.defaultMode.shutdown();
261
275
  current.toolGrouping.shutdown();
262
276
  current.disposeToolExpandedBackground();
277
+ current.toggleRenderCache.shutdown();
263
278
  current.compactMode.shutdown();
264
279
  compactModeHooks = undefined;
265
280
  current.disposeMessageDisplay();