pi-one-ui 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -1
- package/README.en.md +29 -2
- package/README.md +29 -2
- package/extensions/app/config/context.ts +42 -0
- package/extensions/app/config/editor.ts +80 -0
- package/extensions/app/config/footer.ts +291 -0
- package/extensions/app/config/panel.ts +1 -5
- package/extensions/app/config/renderer.ts +1 -6
- package/extensions/app/config/shell.ts +358 -1425
- package/extensions/app/config/store.ts +22 -1
- package/extensions/app/config/values.ts +33 -0
- package/extensions/app/config/working-line.ts +100 -0
- package/extensions/app/overlay/input-router.ts +56 -32
- package/extensions/app/overlay/overlay-manager.ts +183 -21
- package/extensions/app/runtime/event-coordinator.ts +90 -42
- package/extensions/{features → app/runtime}/flush-docked-bash.ts +12 -2
- package/extensions/app/runtime/render-scheduler.ts +35 -31
- package/extensions/app/runtime/tui-runtime.ts +315 -312
- package/extensions/app/settings/controller.ts +202 -0
- package/extensions/app/settings/panel.ts +81 -0
- package/extensions/app/settings/presets.ts +43 -0
- package/extensions/features/context-inspector/index.ts +83 -662
- package/extensions/layouts/context/index.ts +58 -161
- package/extensions/layouts/context/message/user-message.ts +1 -1
- package/extensions/layouts/context/renderer/index.ts +31 -13
- package/extensions/layouts/context/renderer/mouse/interaction.ts +29 -14
- package/extensions/layouts/editor/controller.ts +9 -18
- package/extensions/layouts/editor/factory.ts +3 -3
- package/extensions/layouts/editor/minimalist-editor.ts +3 -0
- package/extensions/layouts/footer/controller.ts +30 -19
- package/extensions/layouts/footer/footer.ts +2 -2
- package/extensions/layouts/overlay/context-inspector.ts +497 -0
- package/extensions/{app → layouts}/overlay/selector-border.ts +5 -5
- package/extensions/{app → layouts}/overlay/selector-controller.ts +1 -1
- package/extensions/layouts/overlay/settings-panel.ts +359 -0
- package/extensions/{app/commands → layouts/overlay}/settings-previews.ts +4 -4
- package/extensions/layouts/working-line/controller.ts +3 -29
- package/extensions/layouts/working-line/working-line.ts +17 -114
- package/extensions/shared/working-line-text.ts +108 -0
- package/package.json +1 -1
- package/extensions/app/host/pi-extension-port.ts +0 -25
- package/extensions/app/host/pi-ui-port.ts +0 -52
- package/extensions/app/host/tui-capabilities.ts +0 -27
- package/extensions/app/ownership/layout-registry.ts +0 -65
- package/extensions/app/ownership.ts +0 -32
- package/extensions/app/panel.ts +0 -708
- package/extensions/app/presets.ts +0 -35
- package/extensions/app/runtime/runtime-state.ts +0 -83
- /package/extensions/{layouts/working-line → app/config}/working-line-messages.ts +0 -0
- /package/extensions/app/{ownership → runtime}/prototype-patch-registry.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,44 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.7.1] - 2026-09-21
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Centralize component composition, settings application, and session lifecycle management in app, with settings and Context Inspector views owned by Overlay Layout.
|
|
14
|
+
- Apply presets with one canonical configuration write and update every affected Layout. Component settings, including Header visibility, apply through their owning Layout; Feature registration switches still require `/reload`.
|
|
15
|
+
- Report corrupt or unreadable configuration files during loading, and distinguish a failed save from a saved configuration that could not be applied to the UI.
|
|
16
|
+
- Coalesce shared Layout redraw requests while preserving forced redraws and session ownership.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Close managed overlays and input listeners during session replacement, and prevent Context Inspector from reopening a preview's parent window in an ended session.
|
|
21
|
+
|
|
22
|
+
### Migration
|
|
23
|
+
|
|
24
|
+
- No configuration migration is required. Existing canonical v1 files and command names remain supported.
|
|
25
|
+
- For an unpinned npm installation, run `pi update npm:pi-one-ui`; for an existing version pin, run `pi install npm:pi-one-ui@0.7.1`. Fully exit and restart Pi after updating the package.
|
|
26
|
+
|
|
27
|
+
## [0.7.0] - 2026-09-13
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Added `components.editor.styles.minimalist.showCwd` to independently show or hide the Editor's working-directory label, including the `/oneui` preview. It defaults to `true`, so existing configurations need no migration. Setting it to `false` preserves `pathDisplay` and leaves Git metadata and Footer directory settings unchanged; edit the JSON configuration and run `/reload` to apply it.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- Restore keyboard focus to the visible Editor after `/oneui` replaces an editor while changing its settings, including native-to-custom transitions, third-party wrappers, and replacement rollback. Closing the panel no longer sends subsequent typing to the detached previous editor in regular or fullscreen mode; existing overlays and later editor owners retain focus.
|
|
36
|
+
|
|
37
|
+
### Migration
|
|
38
|
+
|
|
39
|
+
- No configuration migration is required. Existing configurations keep showing the Editor directory; set `components.editor.styles.minimalist.showCwd` to `false` only if you want to hide it. Footer directory settings remain independent.
|
|
40
|
+
- For an unpinned npm installation, run `pi update npm:pi-one-ui`; for an existing version pin, run `pi install npm:pi-one-ui@0.7.0`. Fully exit and restart Pi after updating the package. Subsequent JSON configuration edits can be applied with `/reload`.
|
|
41
|
+
|
|
42
|
+
### Release verification
|
|
43
|
+
|
|
44
|
+
- Release source: [`10ac3de`](https://github.com/kerolt/pi-one-ui/commit/10ac3dea3f0ac804fef94cbeea937c6f1ae0d347). [CI](https://github.com/kerolt/pi-one-ui/actions/runs/34740987283) and [Publish](https://github.com/kerolt/pi-one-ui/actions/runs/34741047850) completed successfully; `npm run verify` passed 1,165 tests and `npm run pack:check` confirmed 121 package files.
|
|
45
|
+
- At publication, npm `latest` resolved to `0.7.0` with the matching release `gitHead` and [provenance metadata](https://registry.npmjs.org/-/npm/v1/attestations/pi-one-ui@0.7.0). The provenance statement was published at [Sigstore log index 2814335389](https://search.sigstore.dev/?logIndex=2814335389).
|
|
46
|
+
|
|
9
47
|
## [0.6.1] - 2026-09-06
|
|
10
48
|
|
|
11
49
|
### Added
|
|
@@ -115,7 +153,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
115
153
|
|
|
116
154
|
- Reduced rendering lag when expanding settled tool groups by reusing cached child output.
|
|
117
155
|
|
|
118
|
-
[Unreleased]: https://github.com/kerolt/pi-one-ui/compare/v0.
|
|
156
|
+
[Unreleased]: https://github.com/kerolt/pi-one-ui/compare/v0.7.1...HEAD
|
|
157
|
+
[0.7.1]: https://github.com/kerolt/pi-one-ui/compare/v0.7.0...v0.7.1
|
|
158
|
+
[0.7.0]: https://github.com/kerolt/pi-one-ui/compare/v0.6.1...v0.7.0
|
|
119
159
|
[0.6.1]: https://github.com/kerolt/pi-one-ui/compare/v0.6.0...v0.6.1
|
|
120
160
|
[0.6.0]: https://github.com/kerolt/pi-one-ui/compare/v0.5.1...v0.6.0
|
|
121
161
|
[0.5.1]: https://github.com/kerolt/pi-one-ui/compare/v0.5.0...v0.5.1
|
package/README.en.md
CHANGED
|
@@ -76,6 +76,20 @@ Then open the unified settings panel:
|
|
|
76
76
|
/oneui
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
### Upgrading to 0.7.1
|
|
80
|
+
|
|
81
|
+
This release centralizes component composition, settings application, and session lifecycle management. Each preset saves and applies all related component settings in one update, Header settings apply immediately, and session replacement cleans up overlays and input listeners.
|
|
82
|
+
|
|
83
|
+
Existing canonical v1 configurations and commands remain compatible; no migration is required. Corrupt or unreadable configuration files now produce an explicit error.
|
|
84
|
+
|
|
85
|
+
For an unpinned npm installation, run:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pi update npm:pi-one-ui
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
If your installation pins a version, run `pi install npm:pi-one-ui@0.7.1` to update the pin. Fully exit and restart Pi after updating the package.
|
|
92
|
+
|
|
79
93
|
## Configuration
|
|
80
94
|
|
|
81
95
|
### Configuration methods
|
|
@@ -88,9 +102,11 @@ Then open the unified settings panel:
|
|
|
88
102
|
|
|
89
103
|
You can configure the extension in two ways:
|
|
90
104
|
|
|
91
|
-
1. **Interactive settings panel (recommended)**: Run `/oneui` in Pi to adjust
|
|
105
|
+
1. **Interactive settings panel (recommended)**: Run `/oneui` in Pi to adjust component toggles, styles, and border modes. Component settings apply after saving; Features switches apply after `/reload`. Each preset saves all related settings in one update and applies them to the corresponding components.
|
|
92
106
|
2. **Direct JSON editing**: Advanced users can edit the JSON configuration file directly for finer-grained control. After saving edits, run `/reload` in Pi to apply changes. When the file does not exist, safe runtime defaults are used in memory.
|
|
93
107
|
|
|
108
|
+
Corrupt or unreadable configuration files produce an error. Failed saves retain the current settings; if saving succeeds but a component cannot apply the change, the panel reports the application failure explicitly.
|
|
109
|
+
|
|
94
110
|
### Basic configuration example
|
|
95
111
|
|
|
96
112
|
Below is a typical v1 configuration structure:
|
|
@@ -132,7 +148,7 @@ Below is a typical v1 configuration structure:
|
|
|
132
148
|
To keep the configuration section clean and focused, in-depth options, template variables, and color references are organized in dedicated documentation:
|
|
133
149
|
|
|
134
150
|
- **Component options & layout customization**: see [Editor & Footer Configuration Guide (docs/configuration.md)](./docs/configuration.md)
|
|
135
|
-
- **Editor**: Configure `style` (`on` for Minimalist decoration / `off` for Pi native), `borderColorMode` (`static` or `adaptive` to thinking effort), `modelLabel`, and granular Minimalist displays (directory
|
|
151
|
+
- **Editor**: Configure `style` (`on` for Minimalist decoration / `off` for Pi native), `borderColorMode` (`static` or `adaptive` to thinking effort), `modelLabel`, and granular Minimalist displays (directory visibility and format, session name, timer, cost, Git status, etc.). `components.editor.styles.minimalist.showCwd` (since 0.7.0, default `true`) can hide the directory independently of the Footer; `pathDisplay` continues to select its format.
|
|
136
152
|
- **Footer**: Starship-style layout powered by format templates (`$cwd`, `$git_branch`, `$tokens`, `$cost`, etc.), individual `segments` toggles, customizable separators, and context-usage indicators (gauge or text).
|
|
137
153
|
- **WorkingLine**: Built-in live output throughput tracking (appends e.g. `⚡12 tok/s` when a model response runs for at least 500ms, reset per turn).
|
|
138
154
|
- **Settings panel (`/oneui`) customization**: the optional top-level `panel` section controls the overlay placement and size (all fields default to the values shown in the example above).
|
|
@@ -216,6 +232,17 @@ npm run verify
|
|
|
216
232
|
npm run pack:check
|
|
217
233
|
```
|
|
218
234
|
|
|
235
|
+
### Source responsibilities
|
|
236
|
+
|
|
237
|
+
- `extensions/app/runtime/`: composition, shared events, session lifecycle, render scheduling, and patch management.
|
|
238
|
+
- `extensions/app/config/`: configuration types, normalization, and shared storage.
|
|
239
|
+
- `extensions/app/settings/`: settings operations, presets, and the `/oneui` opening flow.
|
|
240
|
+
- `extensions/app/overlay/`: overlay lifecycle, focus coordination, and input routing.
|
|
241
|
+
- `extensions/layouts/`: component behavior and rendering; settings, Context Inspector, and Selector views live in `layouts/overlay/`.
|
|
242
|
+
- `extensions/features/` and `extensions/services/`: features and shared data, composed by app.
|
|
243
|
+
|
|
244
|
+
See [ARCHITECTURE.md](./ARCHITECTURE.md) for module rules and real Pi TUI verification commands.
|
|
245
|
+
|
|
219
246
|
### Test organization
|
|
220
247
|
|
|
221
248
|
All tests run through Vitest and are organized into domain directories:
|
package/README.md
CHANGED
|
@@ -76,6 +76,20 @@ pi install git:github.com/kerolt/pi-one-ui
|
|
|
76
76
|
/oneui
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
### 升级到 0.7.1
|
|
80
|
+
|
|
81
|
+
本次集中管理组件装配、设置应用与会话生命周期。Preset 会一次保存并应用所有相关组件配置,Header 设置即时生效;会话切换时会清理 Overlay 与输入监听。
|
|
82
|
+
|
|
83
|
+
现有 canonical v1 配置和命令保持兼容,无需迁移。配置文件损坏或无法读取时会明确报告错误。
|
|
84
|
+
|
|
85
|
+
未固定版本的 npm 安装可执行:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pi update npm:pi-one-ui
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
若安装时固定了版本,执行 `pi install npm:pi-one-ui@0.7.1` 更新版本指定。更新安装包后,完整退出并重启 Pi。
|
|
92
|
+
|
|
79
93
|
## 配置
|
|
80
94
|
|
|
81
95
|
### 配置方式
|
|
@@ -88,9 +102,11 @@ pi install git:github.com/kerolt/pi-one-ui
|
|
|
88
102
|
|
|
89
103
|
提供两种配置途径:
|
|
90
104
|
|
|
91
|
-
1. **交互式设置面板(推荐)**:在 Pi 会话中运行 `/oneui
|
|
105
|
+
1. **交互式设置面板(推荐)**:在 Pi 会话中运行 `/oneui`,即可调整常用组件开关、样式和边框模式。组件设置保存后即时应用;Features 开关在 `/reload` 后应用。Preset 一次保存全部相关设置并更新对应组件。
|
|
92
106
|
2. **手动编辑配置文件**:高级用户可直接编辑 JSON 配置文件以启用更多细粒度选项。修改保存后,在 Pi 中执行 `/reload` 即可生效。文件不存在时将直接使用内置默认值。
|
|
93
107
|
|
|
108
|
+
配置文件损坏或无法读取时会报告错误。保存失败时保持当前设置;保存成功但组件应用失败时,面板会明确提示应用失败。
|
|
109
|
+
|
|
94
110
|
### 基础配置示例
|
|
95
111
|
|
|
96
112
|
以下为一个典型的 v1 配置文件结构:
|
|
@@ -132,7 +148,7 @@ pi install git:github.com/kerolt/pi-one-ui
|
|
|
132
148
|
各项组件开关、模板变量与颜色字段的完整规范拆分收录于独立文档中,便于按需查阅:
|
|
133
149
|
|
|
134
150
|
- **组件开关与排版定制**:详见 [Editor 与 Footer 配置指南 (docs/configuration.md)](./docs/configuration.md)
|
|
135
|
-
- **Editor**:支持 `style`(`on` 极简装饰 / `off` 原生)、`borderColorMode`(固定色 / 思考档位自适应)、`modelLabel` 及 Minimalist
|
|
151
|
+
- **Editor**:支持 `style`(`on` 极简装饰 / `off` 原生)、`borderColorMode`(固定色 / 思考档位自适应)、`modelLabel` 及 Minimalist 装饰细节(目录显示与格式、会话名、耗时、费用、Git 状态等)。`components.editor.styles.minimalist.showCwd`(0.7.0 起,默认 `true`)可单独隐藏工作目录,不影响 Footer;`pathDisplay` 继续控制目录格式。
|
|
136
152
|
- **Footer**:支持 Starship 风格排版,提供丰富的模板变量(`$cwd`、`$git_branch`、`$tokens`、`$cost` 等),支持通过 `format` 自定义或通过 `segments` 控制各段开关,并可自由配置上下文占用率的展示形式(gauge / text)。
|
|
137
153
|
- **WorkingLine**:内置实时输出速率检测(单次响应持续 >=500ms 自动追加如 `⚡12 tok/s`,按回合独立重置)。
|
|
138
154
|
- **设置面板(`/oneui`)定制**:通过顶层 `panel` 字段控制浮层位置与尺寸,全部字段可省略(缺省值见上文示例)。
|
|
@@ -216,6 +232,17 @@ npm run verify
|
|
|
216
232
|
npm run pack:check
|
|
217
233
|
```
|
|
218
234
|
|
|
235
|
+
### 源码职责
|
|
236
|
+
|
|
237
|
+
- `extensions/app/runtime/`:装配、共享事件、session 生命周期、渲染调度和 patch 管理。
|
|
238
|
+
- `extensions/app/config/`:配置类型、规范化和统一存储。
|
|
239
|
+
- `extensions/app/settings/`:设置操作、Preset 与 `/oneui` 打开流程。
|
|
240
|
+
- `extensions/app/overlay/`:Overlay 生命周期、焦点协作和输入路由。
|
|
241
|
+
- `extensions/layouts/`:组件行为与渲染;设置面板、Context Inspector 和 Selector 视图位于 `layouts/overlay/`。
|
|
242
|
+
- `extensions/features/`、`extensions/services/`:功能实现与共享数据,由 app 统一组织。
|
|
243
|
+
|
|
244
|
+
模块约束和真实 Pi TUI 验证命令见 [ARCHITECTURE.md](./ARCHITECTURE.md)。
|
|
245
|
+
|
|
219
246
|
### 测试组织
|
|
220
247
|
|
|
221
248
|
所有测试统一由 Vitest 执行,并按照功能领域组织在子目录中:
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { booleanValue, recordValue } from "./values.ts";
|
|
2
|
+
|
|
3
|
+
export type UserMessageStyle =
|
|
4
|
+
| "framed"
|
|
5
|
+
| "framed-copy-friendly"
|
|
6
|
+
| "compact"
|
|
7
|
+
| "labeled";
|
|
8
|
+
export type FramedUserMessageStyleConfig = Record<string, never>;
|
|
9
|
+
export type FramedCopyFriendlyUserMessageStyleConfig = Record<string, never>;
|
|
10
|
+
export type CompactUserMessageStyleConfig = Record<string, never>;
|
|
11
|
+
export type LabeledUserMessageStyleConfig = Record<string, never>;
|
|
12
|
+
export type UserMessagesComponentConfig = {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
style: UserMessageStyle;
|
|
15
|
+
styles: {
|
|
16
|
+
framed: FramedUserMessageStyleConfig;
|
|
17
|
+
"framed-copy-friendly": FramedCopyFriendlyUserMessageStyleConfig;
|
|
18
|
+
compact: CompactUserMessageStyleConfig;
|
|
19
|
+
labeled: LabeledUserMessageStyleConfig;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function normalizeUserMessages(
|
|
24
|
+
value: unknown,
|
|
25
|
+
): UserMessagesComponentConfig {
|
|
26
|
+
const source = recordValue(value);
|
|
27
|
+
return {
|
|
28
|
+
enabled: booleanValue(source.enabled, true),
|
|
29
|
+
style:
|
|
30
|
+
source.style === "framed-copy-friendly" ||
|
|
31
|
+
source.style === "compact" ||
|
|
32
|
+
source.style === "labeled"
|
|
33
|
+
? source.style
|
|
34
|
+
: "framed",
|
|
35
|
+
styles: {
|
|
36
|
+
framed: {},
|
|
37
|
+
"framed-copy-friendly": {},
|
|
38
|
+
compact: {},
|
|
39
|
+
labeled: {},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { booleanValue, recordValue } from "./values.ts";
|
|
2
|
+
|
|
3
|
+
export type ModelLabelSource = "id" | "name";
|
|
4
|
+
export type EditorStyle = "on" | "off";
|
|
5
|
+
export type EditorBorderColorMode = "static" | "adaptive";
|
|
6
|
+
export type MinimalistPathDisplayMode = "compact" | "project" | "full";
|
|
7
|
+
export type MinimalistEditorStyleConfig = {
|
|
8
|
+
pathDisplay: MinimalistPathDisplayMode;
|
|
9
|
+
showCwd: boolean;
|
|
10
|
+
showSessionName: boolean;
|
|
11
|
+
showTimer: boolean;
|
|
12
|
+
showCost: boolean;
|
|
13
|
+
showGit: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type MinimalistConfig = MinimalistEditorStyleConfig;
|
|
16
|
+
export type EditorStylesConfig = { minimalist: MinimalistEditorStyleConfig };
|
|
17
|
+
export type EditorComponentConfig = {
|
|
18
|
+
style: EditorStyle;
|
|
19
|
+
borderColorMode: EditorBorderColorMode;
|
|
20
|
+
modelLabel: ModelLabelSource;
|
|
21
|
+
viewportIndicators: boolean;
|
|
22
|
+
styles: EditorStylesConfig;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_EDITOR_METADATA_FORMAT = "$model $provider( $thinking)";
|
|
26
|
+
export const defaultMinimalistStyle: MinimalistEditorStyleConfig = {
|
|
27
|
+
pathDisplay: "compact",
|
|
28
|
+
showCwd: true,
|
|
29
|
+
showSessionName: true,
|
|
30
|
+
showTimer: true,
|
|
31
|
+
showCost: true,
|
|
32
|
+
showGit: true,
|
|
33
|
+
};
|
|
34
|
+
export const defaultEditor: EditorComponentConfig = {
|
|
35
|
+
style: "on",
|
|
36
|
+
borderColorMode: "static",
|
|
37
|
+
modelLabel: "id",
|
|
38
|
+
viewportIndicators: true,
|
|
39
|
+
styles: { minimalist: defaultMinimalistStyle },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function parseEditorModelLabel(value: unknown): ModelLabelSource {
|
|
43
|
+
return value === "name" ? "name" : "id";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parseEditorStyle(value: unknown): EditorStyle {
|
|
47
|
+
return value === "off" ? "off" : "on";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function parseEditorBorderColorMode(
|
|
51
|
+
value: unknown,
|
|
52
|
+
): EditorBorderColorMode {
|
|
53
|
+
return value === "adaptive" ? "adaptive" : "static";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 配置规范化仅生成 canonical 字段。 */
|
|
57
|
+
export function normalizeEditor(value: unknown): EditorComponentConfig {
|
|
58
|
+
const editor = recordValue(value);
|
|
59
|
+
const minimalist = recordValue(recordValue(editor.styles).minimalist);
|
|
60
|
+
return {
|
|
61
|
+
style: editor.enabled === false ? "off" : parseEditorStyle(editor.style),
|
|
62
|
+
borderColorMode: parseEditorBorderColorMode(editor.borderColorMode),
|
|
63
|
+
modelLabel: parseEditorModelLabel(editor.modelLabel),
|
|
64
|
+
viewportIndicators: booleanValue(editor.viewportIndicators, true),
|
|
65
|
+
styles: {
|
|
66
|
+
minimalist: {
|
|
67
|
+
pathDisplay:
|
|
68
|
+
minimalist.pathDisplay === "project" ||
|
|
69
|
+
minimalist.pathDisplay === "full"
|
|
70
|
+
? minimalist.pathDisplay
|
|
71
|
+
: "compact",
|
|
72
|
+
showCwd: booleanValue(minimalist.showCwd, true),
|
|
73
|
+
showSessionName: booleanValue(minimalist.showSessionName, true),
|
|
74
|
+
showTimer: booleanValue(minimalist.showTimer, true),
|
|
75
|
+
showCost: booleanValue(minimalist.showCost, true),
|
|
76
|
+
showGit: booleanValue(minimalist.showGit, true),
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { type ModelLabelSource, parseEditorModelLabel } from "./editor.ts";
|
|
2
|
+
import { booleanValue, recordValue } from "./values.ts";
|
|
3
|
+
|
|
4
|
+
export type ContextStyle = "text" | "gauge" | "text+gauge";
|
|
5
|
+
export type SeparatorStyle = "pipe" | "dot" | "chevron" | "none";
|
|
6
|
+
export type FooterStyle = "native" | "starship" | "hidden";
|
|
7
|
+
export type CompactFooterMaxLines = 1 | 2 | 3 | "unlimited";
|
|
8
|
+
export type ContextThresholds = { warning: number; error: number };
|
|
9
|
+
export type PathDisplayMode = "basename" | "full";
|
|
10
|
+
export type PathDisplayConfig = { mode: PathDisplayMode; depth: number };
|
|
11
|
+
export type GitBranchMaxLength = "full" | number;
|
|
12
|
+
export type GitBranchConfig = { maxLength: GitBranchMaxLength };
|
|
13
|
+
export type GitCommitConfig = {
|
|
14
|
+
hashLength: number;
|
|
15
|
+
onlyDetached: boolean;
|
|
16
|
+
showTag: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type GitMetricsConfig = {
|
|
19
|
+
onlyNonzero: boolean;
|
|
20
|
+
ignoreSubmodules: boolean;
|
|
21
|
+
};
|
|
22
|
+
export type ExtensionStatusPlacement = "off" | "left" | "middle" | "right";
|
|
23
|
+
export type ExtensionStatusColorMode = "zentui" | "original";
|
|
24
|
+
export type ExtensionStatusesConfig = {
|
|
25
|
+
defaultPlacement: ExtensionStatusPlacement;
|
|
26
|
+
placements: Record<string, ExtensionStatusPlacement>;
|
|
27
|
+
colorModes: Record<string, ExtensionStatusColorMode>;
|
|
28
|
+
};
|
|
29
|
+
export type FooterSegmentsConfig = {
|
|
30
|
+
cwd: boolean;
|
|
31
|
+
sessionName: boolean;
|
|
32
|
+
gitBranch: boolean;
|
|
33
|
+
gitStatus: boolean;
|
|
34
|
+
gitCounts: boolean;
|
|
35
|
+
gitCommit: boolean;
|
|
36
|
+
gitMetrics: boolean;
|
|
37
|
+
runtime: boolean;
|
|
38
|
+
modelInfo: boolean;
|
|
39
|
+
context: boolean;
|
|
40
|
+
tokens: boolean;
|
|
41
|
+
cost: boolean;
|
|
42
|
+
sessionDuration: boolean;
|
|
43
|
+
username: boolean;
|
|
44
|
+
time: boolean;
|
|
45
|
+
os: boolean;
|
|
46
|
+
packageVersion: boolean;
|
|
47
|
+
};
|
|
48
|
+
export type StarshipFooterStyleConfig = {
|
|
49
|
+
format: string;
|
|
50
|
+
responsive: boolean;
|
|
51
|
+
compactFormat: string;
|
|
52
|
+
compactMaxLines: CompactFooterMaxLines;
|
|
53
|
+
separator: SeparatorStyle;
|
|
54
|
+
contextStyle: ContextStyle;
|
|
55
|
+
contextThresholds: ContextThresholds;
|
|
56
|
+
pathDisplay: PathDisplayConfig;
|
|
57
|
+
segments: FooterSegmentsConfig;
|
|
58
|
+
gitBranch: GitBranchConfig;
|
|
59
|
+
gitCommit: GitCommitConfig;
|
|
60
|
+
gitMetrics: GitMetricsConfig;
|
|
61
|
+
extensionStatuses: ExtensionStatusesConfig;
|
|
62
|
+
};
|
|
63
|
+
export type FooterComponentConfig = {
|
|
64
|
+
style: FooterStyle;
|
|
65
|
+
modelLabel: ModelLabelSource;
|
|
66
|
+
styles: { starship: StarshipFooterStyleConfig };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const DEFAULT_COMPACT_FOOTER_FORMAT =
|
|
70
|
+
"$cwd$wrap(in $session_name)$wrap(on $git_branch) $git_status$wrap$context$wrap_sep$tokens";
|
|
71
|
+
export const FOOTER_FORMAT_VARIABLES = [
|
|
72
|
+
"cwd",
|
|
73
|
+
"session_name",
|
|
74
|
+
"git_branch",
|
|
75
|
+
"git_status",
|
|
76
|
+
"git_state",
|
|
77
|
+
"runtime",
|
|
78
|
+
"model",
|
|
79
|
+
"provider",
|
|
80
|
+
"session_duration",
|
|
81
|
+
"username",
|
|
82
|
+
"os",
|
|
83
|
+
"time",
|
|
84
|
+
"context",
|
|
85
|
+
"tokens",
|
|
86
|
+
"cache_read",
|
|
87
|
+
"cache_write",
|
|
88
|
+
"cost",
|
|
89
|
+
"subscription",
|
|
90
|
+
"auto_compaction",
|
|
91
|
+
"package",
|
|
92
|
+
"package_version",
|
|
93
|
+
"git_commit",
|
|
94
|
+
"git_tag",
|
|
95
|
+
"git_metrics",
|
|
96
|
+
"git_added",
|
|
97
|
+
"git_deleted",
|
|
98
|
+
"sep",
|
|
99
|
+
] as const;
|
|
100
|
+
export const FOOTER_FORMAT_ALIASES: Record<string, string> = {
|
|
101
|
+
directory: "cwd",
|
|
102
|
+
branch: "git_branch",
|
|
103
|
+
status: "git_status",
|
|
104
|
+
state: "git_state",
|
|
105
|
+
commit: "git_commit",
|
|
106
|
+
tag: "git_tag",
|
|
107
|
+
duration: "session_duration",
|
|
108
|
+
separator: "sep",
|
|
109
|
+
};
|
|
110
|
+
export const defaultFooterSegments: FooterSegmentsConfig = {
|
|
111
|
+
cwd: true,
|
|
112
|
+
sessionName: true,
|
|
113
|
+
gitBranch: true,
|
|
114
|
+
gitStatus: true,
|
|
115
|
+
gitCounts: false,
|
|
116
|
+
gitCommit: false,
|
|
117
|
+
gitMetrics: false,
|
|
118
|
+
runtime: true,
|
|
119
|
+
modelInfo: false,
|
|
120
|
+
context: true,
|
|
121
|
+
tokens: true,
|
|
122
|
+
cost: true,
|
|
123
|
+
sessionDuration: false,
|
|
124
|
+
username: false,
|
|
125
|
+
time: false,
|
|
126
|
+
os: false,
|
|
127
|
+
packageVersion: false,
|
|
128
|
+
};
|
|
129
|
+
export const defaultStarshipStyle: StarshipFooterStyleConfig = {
|
|
130
|
+
format: "",
|
|
131
|
+
responsive: true,
|
|
132
|
+
compactFormat: DEFAULT_COMPACT_FOOTER_FORMAT,
|
|
133
|
+
compactMaxLines: 2,
|
|
134
|
+
separator: "pipe",
|
|
135
|
+
contextStyle: "text",
|
|
136
|
+
contextThresholds: { warning: 70, error: 90 },
|
|
137
|
+
pathDisplay: { mode: "basename", depth: 0 },
|
|
138
|
+
segments: defaultFooterSegments,
|
|
139
|
+
gitBranch: { maxLength: "full" },
|
|
140
|
+
gitCommit: { hashLength: 7, onlyDetached: true, showTag: true },
|
|
141
|
+
gitMetrics: { onlyNonzero: true, ignoreSubmodules: false },
|
|
142
|
+
extensionStatuses: {
|
|
143
|
+
defaultPlacement: "right",
|
|
144
|
+
placements: {},
|
|
145
|
+
colorModes: {},
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
export const defaultFooter: FooterComponentConfig = {
|
|
149
|
+
style: "starship",
|
|
150
|
+
modelLabel: "id",
|
|
151
|
+
styles: { starship: defaultStarshipStyle },
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export function isSeparatorStyle(value: unknown): value is SeparatorStyle {
|
|
155
|
+
return (
|
|
156
|
+
value === "pipe" ||
|
|
157
|
+
value === "dot" ||
|
|
158
|
+
value === "chevron" ||
|
|
159
|
+
value === "none"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
export function parseSeparatorStyle(value: unknown): SeparatorStyle {
|
|
163
|
+
return isSeparatorStyle(value) ? value : "pipe";
|
|
164
|
+
}
|
|
165
|
+
export function parseContextStyle(value: unknown): ContextStyle {
|
|
166
|
+
return value === "gauge" || value === "text+gauge" ? value : "text";
|
|
167
|
+
}
|
|
168
|
+
export function parseCompactFooterMaxLines(
|
|
169
|
+
value: unknown,
|
|
170
|
+
): CompactFooterMaxLines {
|
|
171
|
+
return value === 1 || value === 2 || value === 3 || value === "unlimited"
|
|
172
|
+
? value
|
|
173
|
+
: 2;
|
|
174
|
+
}
|
|
175
|
+
export function isExtensionStatusPlacement(
|
|
176
|
+
value: unknown,
|
|
177
|
+
): value is ExtensionStatusPlacement {
|
|
178
|
+
return (
|
|
179
|
+
value === "off" ||
|
|
180
|
+
value === "left" ||
|
|
181
|
+
value === "middle" ||
|
|
182
|
+
value === "right"
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
export function isExtensionStatusColorMode(
|
|
186
|
+
value: unknown,
|
|
187
|
+
): value is ExtensionStatusColorMode {
|
|
188
|
+
return value === "zentui" || value === "original";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function thresholds(value: unknown): ContextThresholds {
|
|
192
|
+
const source = recordValue(value);
|
|
193
|
+
const percent = (input: unknown, defaultValue: number) =>
|
|
194
|
+
typeof input === "number" && Number.isFinite(input)
|
|
195
|
+
? Math.max(0, Math.min(100, Math.round(input)))
|
|
196
|
+
: defaultValue;
|
|
197
|
+
const warning = percent(source.warning, 70);
|
|
198
|
+
const error = percent(source.error, 90);
|
|
199
|
+
return { warning: Math.min(warning, error), error: Math.max(warning, error) };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** 每个 Footer 字段独立规范化,返回完整的 canonical 配置。 */
|
|
203
|
+
export function normalizeFooter(value: unknown): FooterComponentConfig {
|
|
204
|
+
const footer = recordValue(value);
|
|
205
|
+
const source = recordValue(recordValue(footer.styles).starship);
|
|
206
|
+
const path = recordValue(source.pathDisplay);
|
|
207
|
+
const branch = recordValue(source.gitBranch);
|
|
208
|
+
const commit = recordValue(source.gitCommit);
|
|
209
|
+
const metrics = recordValue(source.gitMetrics);
|
|
210
|
+
const statuses = recordValue(source.extensionStatuses);
|
|
211
|
+
const segments = recordValue(source.segments);
|
|
212
|
+
const hashLength =
|
|
213
|
+
typeof commit.hashLength === "number"
|
|
214
|
+
? commit.hashLength
|
|
215
|
+
: Number(commit.hashLength);
|
|
216
|
+
return {
|
|
217
|
+
style:
|
|
218
|
+
footer.style === "native" || footer.style === "hidden"
|
|
219
|
+
? footer.style
|
|
220
|
+
: "starship",
|
|
221
|
+
modelLabel: parseEditorModelLabel(footer.modelLabel),
|
|
222
|
+
styles: {
|
|
223
|
+
starship: {
|
|
224
|
+
format: typeof source.format === "string" ? source.format : "",
|
|
225
|
+
responsive: booleanValue(source.responsive, true),
|
|
226
|
+
compactFormat:
|
|
227
|
+
typeof source.compactFormat === "string" &&
|
|
228
|
+
source.compactFormat.length > 0
|
|
229
|
+
? source.compactFormat
|
|
230
|
+
: DEFAULT_COMPACT_FOOTER_FORMAT,
|
|
231
|
+
compactMaxLines: parseCompactFooterMaxLines(source.compactMaxLines),
|
|
232
|
+
separator: parseSeparatorStyle(source.separator),
|
|
233
|
+
contextStyle: parseContextStyle(source.contextStyle),
|
|
234
|
+
contextThresholds: thresholds(source.contextThresholds),
|
|
235
|
+
pathDisplay: {
|
|
236
|
+
mode: path.mode === "full" ? "full" : "basename",
|
|
237
|
+
depth:
|
|
238
|
+
typeof path.depth === "number" &&
|
|
239
|
+
Number.isFinite(path.depth) &&
|
|
240
|
+
path.depth >= 0
|
|
241
|
+
? Math.min(5, Math.floor(path.depth))
|
|
242
|
+
: 0,
|
|
243
|
+
},
|
|
244
|
+
segments: Object.fromEntries(
|
|
245
|
+
Object.entries(defaultFooterSegments).map(([key, enabled]) => [
|
|
246
|
+
key,
|
|
247
|
+
booleanValue(segments[key], enabled),
|
|
248
|
+
]),
|
|
249
|
+
) as FooterSegmentsConfig,
|
|
250
|
+
gitBranch: {
|
|
251
|
+
maxLength:
|
|
252
|
+
typeof branch.maxLength === "number" &&
|
|
253
|
+
Number.isInteger(branch.maxLength) &&
|
|
254
|
+
branch.maxLength > 0
|
|
255
|
+
? branch.maxLength
|
|
256
|
+
: "full",
|
|
257
|
+
},
|
|
258
|
+
gitCommit: {
|
|
259
|
+
hashLength: Number.isFinite(hashLength)
|
|
260
|
+
? Math.min(40, Math.max(4, Math.round(hashLength)))
|
|
261
|
+
: 7,
|
|
262
|
+
onlyDetached: booleanValue(commit.onlyDetached, true),
|
|
263
|
+
showTag: booleanValue(commit.showTag, true),
|
|
264
|
+
},
|
|
265
|
+
gitMetrics: {
|
|
266
|
+
onlyNonzero: booleanValue(metrics.onlyNonzero, true),
|
|
267
|
+
ignoreSubmodules: booleanValue(metrics.ignoreSubmodules, false),
|
|
268
|
+
},
|
|
269
|
+
extensionStatuses: {
|
|
270
|
+
defaultPlacement: isExtensionStatusPlacement(
|
|
271
|
+
statuses.defaultPlacement,
|
|
272
|
+
)
|
|
273
|
+
? statuses.defaultPlacement
|
|
274
|
+
: "right",
|
|
275
|
+
placements: Object.fromEntries(
|
|
276
|
+
Object.entries(recordValue(statuses.placements)).filter(
|
|
277
|
+
(entry): entry is [string, ExtensionStatusPlacement] =>
|
|
278
|
+
isExtensionStatusPlacement(entry[1]),
|
|
279
|
+
),
|
|
280
|
+
),
|
|
281
|
+
colorModes: Object.fromEntries(
|
|
282
|
+
Object.entries(recordValue(statuses.colorModes)).filter(
|
|
283
|
+
(entry): entry is [string, ExtensionStatusColorMode] =>
|
|
284
|
+
isExtensionStatusColorMode(entry[1]),
|
|
285
|
+
),
|
|
286
|
+
),
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
@@ -109,9 +109,5 @@ export function normalizePanelOverlay(input: unknown): PanelOverlayConfig {
|
|
|
109
109
|
* every /oneui open so JSON edits apply without `/reload`.
|
|
110
110
|
*/
|
|
111
111
|
export function loadPanelOverlayConfig(): PanelOverlayConfig {
|
|
112
|
-
|
|
113
|
-
return normalizePanelOverlay(configStore.read().panel);
|
|
114
|
-
} catch {
|
|
115
|
-
return normalizePanelOverlay(undefined);
|
|
116
|
-
}
|
|
112
|
+
return normalizePanelOverlay(configStore.read().panel);
|
|
117
113
|
}
|
|
@@ -364,12 +364,7 @@ export const config: Config = loadConfig();
|
|
|
364
364
|
* Loads and normalizes the renderer projection from the shared store.
|
|
365
365
|
*/
|
|
366
366
|
function loadConfig(): Config {
|
|
367
|
-
|
|
368
|
-
return normalizeConfig(rendererConfigFrom(configStore.read()));
|
|
369
|
-
} catch {
|
|
370
|
-
// Ignore bad config and fall back to defaults.
|
|
371
|
-
}
|
|
372
|
-
return { ...DEFAULT_CONFIG };
|
|
367
|
+
return normalizeConfig(rendererConfigFrom(configStore.read()));
|
|
373
368
|
}
|
|
374
369
|
|
|
375
370
|
/**
|