pi-one-ui 0.7.0 → 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.
Files changed (49) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.en.md +19 -6
  3. package/README.md +19 -6
  4. package/extensions/app/config/context.ts +42 -0
  5. package/extensions/app/config/editor.ts +80 -0
  6. package/extensions/app/config/footer.ts +291 -0
  7. package/extensions/app/config/panel.ts +1 -5
  8. package/extensions/app/config/renderer.ts +1 -6
  9. package/extensions/app/config/shell.ts +358 -1435
  10. package/extensions/app/config/store.ts +22 -1
  11. package/extensions/app/config/values.ts +33 -0
  12. package/extensions/app/config/working-line.ts +100 -0
  13. package/extensions/app/overlay/input-router.ts +56 -32
  14. package/extensions/app/overlay/overlay-manager.ts +183 -21
  15. package/extensions/app/runtime/event-coordinator.ts +90 -42
  16. package/extensions/{features → app/runtime}/flush-docked-bash.ts +12 -2
  17. package/extensions/app/runtime/render-scheduler.ts +35 -31
  18. package/extensions/app/runtime/tui-runtime.ts +315 -312
  19. package/extensions/app/settings/controller.ts +202 -0
  20. package/extensions/app/settings/panel.ts +81 -0
  21. package/extensions/app/settings/presets.ts +43 -0
  22. package/extensions/features/context-inspector/index.ts +83 -662
  23. package/extensions/layouts/context/index.ts +58 -161
  24. package/extensions/layouts/context/message/user-message.ts +1 -1
  25. package/extensions/layouts/context/renderer/index.ts +31 -13
  26. package/extensions/layouts/context/renderer/mouse/interaction.ts +29 -14
  27. package/extensions/layouts/editor/controller.ts +9 -18
  28. package/extensions/layouts/editor/factory.ts +3 -3
  29. package/extensions/layouts/footer/controller.ts +30 -19
  30. package/extensions/layouts/footer/footer.ts +2 -2
  31. package/extensions/layouts/overlay/context-inspector.ts +497 -0
  32. package/extensions/{app → layouts}/overlay/selector-border.ts +5 -5
  33. package/extensions/{app → layouts}/overlay/selector-controller.ts +1 -1
  34. package/extensions/layouts/overlay/settings-panel.ts +359 -0
  35. package/extensions/{app/commands → layouts/overlay}/settings-previews.ts +4 -4
  36. package/extensions/layouts/working-line/controller.ts +3 -29
  37. package/extensions/layouts/working-line/working-line.ts +17 -114
  38. package/extensions/shared/working-line-text.ts +108 -0
  39. package/package.json +1 -1
  40. package/extensions/app/host/pi-extension-port.ts +0 -25
  41. package/extensions/app/host/pi-ui-port.ts +0 -52
  42. package/extensions/app/host/tui-capabilities.ts +0 -27
  43. package/extensions/app/ownership/layout-registry.ts +0 -65
  44. package/extensions/app/ownership.ts +0 -32
  45. package/extensions/app/panel.ts +0 -770
  46. package/extensions/app/presets.ts +0 -35
  47. package/extensions/app/runtime/runtime-state.ts +0 -83
  48. /package/extensions/{layouts/working-line → app/config}/working-line-messages.ts +0 -0
  49. /package/extensions/app/{ownership → runtime}/prototype-patch-registry.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,24 @@ 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
+
9
27
  ## [0.7.0] - 2026-09-13
10
28
 
11
29
  ### Added
@@ -21,6 +39,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
21
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.
22
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`.
23
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
+
24
47
  ## [0.6.1] - 2026-09-06
25
48
 
26
49
  ### Added
@@ -130,7 +153,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
130
153
 
131
154
  - Reduced rendering lag when expanding settled tool groups by reusing cached child output.
132
155
 
133
- [Unreleased]: https://github.com/kerolt/pi-one-ui/compare/v0.7.0...HEAD
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
134
158
  [0.7.0]: https://github.com/kerolt/pi-one-ui/compare/v0.6.1...v0.7.0
135
159
  [0.6.1]: https://github.com/kerolt/pi-one-ui/compare/v0.6.0...v0.6.1
136
160
  [0.6.0]: https://github.com/kerolt/pi-one-ui/compare/v0.5.1...v0.6.0
package/README.en.md CHANGED
@@ -76,9 +76,11 @@ Then open the unified settings panel:
76
76
  /oneui
77
77
  ```
78
78
 
79
- ### Upgrading to 0.7.0
79
+ ### Upgrading to 0.7.1
80
80
 
81
- This release adds an Editor working-directory visibility switch and fixes invisible input after changing Editor settings and closing `/oneui`. Existing configurations need no migration; the directory remains visible by default.
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.
82
84
 
83
85
  For an unpinned npm installation, run:
84
86
 
@@ -86,9 +88,7 @@ For an unpinned npm installation, run:
86
88
  pi update npm:pi-one-ui
87
89
  ```
88
90
 
89
- If your installation pins a version, run `pi install npm:pi-one-ui@0.7.0` to update the pin. Fully exit and restart Pi after updating the package.
90
-
91
- To hide the Editor directory, set `components.editor.styles.minimalist.showCwd: false`; Footer directory visibility is configured independently. See the complete example in the [configuration guide](./docs/configuration.md#隐藏-editor-目录在-footer-左侧显示).
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
92
 
93
93
  ## Configuration
94
94
 
@@ -102,9 +102,11 @@ To hide the Editor directory, set `components.editor.styles.minimalist.showCwd:
102
102
 
103
103
  You can configure the extension in two ways:
104
104
 
105
- 1. **Interactive settings panel (recommended)**: Run `/oneui` in Pi to adjust common component toggles, styles, and border modes via a visual menu. Changes take effect immediately and are persisted automatically.
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.
106
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.
107
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
+
108
110
  ### Basic configuration example
109
111
 
110
112
  Below is a typical v1 configuration structure:
@@ -230,6 +232,17 @@ npm run verify
230
232
  npm run pack:check
231
233
  ```
232
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
+
233
246
  ### Test organization
234
247
 
235
248
  All tests run through Vitest and are organized into domain directories:
package/README.md CHANGED
@@ -76,9 +76,11 @@ pi install git:github.com/kerolt/pi-one-ui
76
76
  /oneui
77
77
  ```
78
78
 
79
- ### 升级到 0.7.0
79
+ ### 升级到 0.7.1
80
80
 
81
- 本次新增 Editor 工作目录显示开关,并修复修改 Editor 配置后关闭 `/oneui` 导致输入不可见的问题。现有配置无需迁移,工作目录仍默认显示。
81
+ 本次集中管理组件装配、设置应用与会话生命周期。Preset 会一次保存并应用所有相关组件配置,Header 设置即时生效;会话切换时会清理 Overlay 与输入监听。
82
+
83
+ 现有 canonical v1 配置和命令保持兼容,无需迁移。配置文件损坏或无法读取时会明确报告错误。
82
84
 
83
85
  未固定版本的 npm 安装可执行:
84
86
 
@@ -86,9 +88,7 @@ pi install git:github.com/kerolt/pi-one-ui
86
88
  pi update npm:pi-one-ui
87
89
  ```
88
90
 
89
- 若安装时固定了版本,执行 `pi install npm:pi-one-ui@0.7.0` 更新版本指定。更新安装包后,完整退出并重启 Pi。
90
-
91
- 如需隐藏 Editor 目录,设置 `components.editor.styles.minimalist.showCwd: false`;Footer 中的目录显示独立配置。完整示例见 [配置指南](./docs/configuration.md#隐藏-editor-目录在-footer-左侧显示)。
91
+ 若安装时固定了版本,执行 `pi install npm:pi-one-ui@0.7.1` 更新版本指定。更新安装包后,完整退出并重启 Pi。
92
92
 
93
93
  ## 配置
94
94
 
@@ -102,9 +102,11 @@ pi update npm:pi-one-ui
102
102
 
103
103
  提供两种配置途径:
104
104
 
105
- 1. **交互式设置面板(推荐)**:在 Pi 会话中运行 `/oneui`,即可在可视化面板中即时调整常用组件开关、样式和边框模式。修改即刻生效并自动持久化。
105
+ 1. **交互式设置面板(推荐)**:在 Pi 会话中运行 `/oneui`,即可调整常用组件开关、样式和边框模式。组件设置保存后即时应用;Features 开关在 `/reload` 后应用。Preset 一次保存全部相关设置并更新对应组件。
106
106
  2. **手动编辑配置文件**:高级用户可直接编辑 JSON 配置文件以启用更多细粒度选项。修改保存后,在 Pi 中执行 `/reload` 即可生效。文件不存在时将直接使用内置默认值。
107
107
 
108
+ 配置文件损坏或无法读取时会报告错误。保存失败时保持当前设置;保存成功但组件应用失败时,面板会明确提示应用失败。
109
+
108
110
  ### 基础配置示例
109
111
 
110
112
  以下为一个典型的 v1 配置文件结构:
@@ -230,6 +232,17 @@ npm run verify
230
232
  npm run pack:check
231
233
  ```
232
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
+
233
246
  ### 测试组织
234
247
 
235
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
- try {
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
- try {
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
  /**