@yinan_chen/pi-claude-code-ui 0.9.0 → 0.9.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/README.md CHANGED
@@ -1,57 +1,59 @@
1
1
  # @yinan_chen/pi-claude-code-ui
2
2
 
3
+ English | [简体中文](README.zh-CN.md)
4
+
3
5
  ![preview](assets/preview.png)
4
6
 
5
- ### 演示
7
+ ### Demo
6
8
 
7
9
  ![demo](https://github.com/user-attachments/assets/d264935a-79fc-434e-be97-05c8210144fe)
8
10
 
9
- 动图预览(不渲染视频的平台,如 npm): ![gif](assets/demo.gif)
11
+ Animated GIF preview (for platforms that don't render video, such as npm): ![gif](assets/demo.gif)
10
12
 
11
- > 原名 lean-tidy —— Claude Code 风格的 Pi 界面增强扩展:Claude 式对话转录视图、富文本 diff 渲染、子代理面板与工作状态提示。
13
+ > Formerly lean-tidy — a Claude Code-style UI enhancement extension for Pi: Claude-style conversation transcript views, rich diff rendering, subagent panels, and working-state hints.
12
14
 
13
- ## 安装
15
+ ## Install
14
16
 
15
17
  ```bash
16
18
  pi install npm:@yinan_chen/pi-claude-code-ui
17
19
  ```
18
20
 
19
- 试运行(不写入配置):
21
+ Trial run (doesn't write to your config):
20
22
 
21
23
  ```bash
22
24
  pi -e npm:@yinan_chen/pi-claude-code-ui
23
25
  ```
24
26
 
25
- ## 功能
27
+ ## Features
26
28
 
27
- - **Claude 式视图** — 对话转录适配器,Pi 原生回退
28
- - **吸附消息泡(sticky)** — 滚动时顶部吸附显示当前正在回答的用户消息原文,点击回位(v0.2.0 内置,原 sticky-first-message 扩展)
29
- - **Skill 折叠(recollapse)** — 展开的 skill 全文自动折回 `/skill:名 参数`,覆盖 /tree、/fork 回填等四处盲区(v0.2.0 内置,原 skill-block-recollapse 扩展)
30
- - **第三方渲染全接管** — 任何扩展的卡片(含未来未适配的)自动渲染为统一的 `● 卡片` 风格,工具/消息/条目三层全覆盖,零配置零适配;分发层拦截不受加载顺序影响(v0.7.0)
31
- - **富 diff 渲染** — edit/write 工具的语法高亮 diff(vendored, MIT)
32
- - **子代理面板** — 后台 agent 状态一目了然
33
- - **工作状态提示** — working message / running effect
34
- - **只读工具美化** — read/bash/grep/find/ls 结果渲染
35
- - **设置面板** — `/skin-setting` 即改即存调整全部皮肤配置,`/skin-setting default` 一键恢复默认(v0.9.0)
29
+ - **Claude-style views** — conversation transcript adapter, with Pi native fallback
30
+ - **Sticky message bubble** — pins the user message currently being answered at the top while scrolling; click to jump back (built in since v0.2.0; formerly the sticky-first-message extension)
31
+ - **Skill recollapse** — expanded skill invocations automatically fold back to `/skill:name args`, covering four blind spots such as /tree and /fork backfill (built in since v0.2.0; formerly the skill-block-recollapse extension)
32
+ - **Full third-party renderer takeover** — cards from any extension (including future unmatched ones) render automatically in a unified `● card` style, covering tools / messages / entries at all three layers, zero config and zero adaptation; dispatch-layer interception is unaffected by load order (v0.7.0)
33
+ - **Rich diff rendering** — syntax-highlighted diffs for edit/write tools (vendored, MIT)
34
+ - **Subagent panel** — background agent status at a glance
35
+ - **Working-state hints** — working message / running effect
36
+ - **Polished read-only tools** — rendering for read / bash / grep / find / ls results
37
+ - **Skin Settings panel** — `/skin-setting` adjusts every skin setting with save-as-you-edit; `/skin-setting default` resets to defaults in one shot (v0.9.0)
36
38
 
37
- ## 配置
39
+ ## Configuration
38
40
 
39
- ### 推荐方式:`/skin-setting` 设置面板
41
+ ### Recommended: the `/skin-setting` panel
40
42
 
41
- 在 pi 会话里输入 `/skin-setting` 打开居中 overlay 设置面板,即改即存——每次改动校验后立即写入配置文件并刷新面板行,没有"保存/放弃"负担:
43
+ Type `/skin-setting` in a pi session to open the centered overlay Skin Settings panel, where changes save as you make them — every edit is validated, written to the config file immediately, and reflected in the panel row, with no save/discard burden:
42
44
 
43
- - 内置搜索过滤,25 个配置键快速定位;每行附中文说明与默认值
44
- - 枚举/布尔键回车在合法值间循环切换,不会选错;数字/字符串键回车进入输入框,带范围校验(如 0–100、0–4)
45
- - 大多数键是热键,改动关闭面板后整屏重绘即刻可见;加载期固化的冷键常驻 `⟳ 需 /reload 生效` 标注,改动时会再提醒一次
46
- - `foreignCardSkip` 黑名单只读展示,看得见状态又不会误编辑
47
- - `/skin-setting default`:经确认对话框一键恢复全部默认(只清除本皮肤的配置键,文件里手写的其他内容保留)
48
- - 非 TUI 模式(RPC / JSON / print)下运行会提示配置文件路径,请改用文件方式
45
+ - Built-in search filtering to quickly locate any of the 25 config keys; each row shows its description and default value
46
+ - Enum / boolean keys cycle through legal values on Enter, so you can't pick an invalid one; number / string keys open an input box on Enter with range validation (e.g. 0–100, 0–4)
47
+ - Most keys are live settings — once you change them and close the panel, a full redraw makes them visible at once; reload-required settings are fixed at load time, carry a persistent `⟳ needs /reload` annotation, and get one extra reminder when changed
48
+ - The `foreignCardSkip` blocklist is shown read-only — its state stays visible without risk of accidental edits
49
+ - `/skin-setting default`: one-shot reset to defaults after a confirmation dialog (only this skin's config keys are removed; anything else you wrote in the file is kept)
50
+ - In non-TUI modes (RPC / JSON / print) it prints the config file path instead — use the file-based approach below
49
51
 
50
- 任何时刻可用 **F5** 整屏重绘(排残影的习惯操作)。
52
+ **F5** forces a full-screen redraw at any time (the habitual fix for leftover artifacts).
51
53
 
52
- ### 手改配置文件
54
+ ### Editing the config file directly
53
55
 
54
- 也可以直接编辑 `~/.pi/agent/lean-tidy.json`(不存在时全部使用默认值)。手改后需 `/reload` 生效;设置面板的改动则是热键即时生效、冷键(下表标 `⟳` 者)需 `/reload`:
56
+ You can also edit `~/.pi/agent/lean-tidy.json` directly (when the file doesn't exist, all defaults apply). Manual edits take effect after `/reload`; panel edits apply instantly for live settings, while reload-required settings (those marked `⟳` in the table below) need `/reload`:
55
57
 
56
58
  ```json
57
59
  {
@@ -85,36 +87,36 @@ pi -e npm:@yinan_chen/pi-claude-code-ui
85
87
  }
86
88
  ```
87
89
 
88
- | 键 | 默认 | 说明 |
90
+ | Key | Default | Description |
89
91
  |---|---|---|
90
- | `profile` ⟳ | `"claude"` | 整体风格:`claude` / `native`(pi 原生) |
91
- | `skillRendering` | `"native"` | skill 块样式:pi 原生折叠行 / `claude` 两行风格 |
92
- | `subagentRendering` ⟳ | `"claude"` | 子代理完成通知:`claude` 分支风格 / `native` 保留包内面板 |
93
- | `detailsBackground` | `"selectedBg"` | 详情背景色(主题色键名,可选 `customMessageBg`/`toolPendingBg`/`toolSuccessBg`/`toolErrorBg`/`""`) |
94
- | `bash/code/errorPreviewLines` | `0`/`0`/`0` | 三类工具结果的预览行数(0–100;0 = 默认折叠) |
95
- | `liveThinking` | `true` | 思考过程流式展示 |
96
- | `thinkingTailLines` | `5` | 思考尾行数 |
97
- | `foldAnimMs` | `240` | 折叠动画时长(ms,0 关闭) |
98
- | `stickyIncludeCustom` ⟳ | `false` | 吸附泡是否包含扩展注入/跨会话转发的旁路消息 |
99
- | `skillResumePreview` ⟳ | `true` | `/resume` 列表预览是否折回 `[skill] 名` 单行 |
100
- | `resultPrefix` | `"└"` | 工具结果行拐角前缀(制表符族,等宽字体最稳;可换 `↳` 或 Claude Code 官方的 `⎿`,后两者在部分字体下会变宽) |
101
- | `resultPrefixGap` | `1` | 前缀符号与内容之间的空格数(0–4) |
102
- | `normalizeForeignRenderers` | `true` | 第三方扩展卡片输出里行首 `⎿` 自动规范化为 `resultPrefix`(任何未适配扩展的渲染都能统一风格;关闭可看第三方原版) |
103
- | `workflowRendering` ⟳ | `"claude"` | workflow 完成卡片样式:`● Workflow(name)` 风格 / `native` 保留包内卡片。分发层拦截实现,不受包加载顺序影响 |
104
- | `foreignCardStyle` | `"generic"` | 未适配扩展的卡片:自动接管为通用 `● 卡片`(零配置);`symbols` = 仅统一符号不接管布局 |
105
- | `foreignCardSkip` | `[]` | 不接管的 customType 黑名单(交互型第三方卡片异常时按类型跳过) |
106
- | `diff.diffViewMode` | `"auto"` | diff 视图:`auto`/`split`/`unified` |
107
- | `diff.diffIndicatorMode` | `"bars"` | 变更指示条:`bars`/`classic`/`none` |
108
- | `diff.diffSplitMinWidth` | `120` | 宽于此才用 split 视图 |
109
- | `diff.edit/writeDiffCollapsedLines` | `0`/`0` | edit/write 默认折叠行数(0 = 默认展开) |
110
- | `diff.diffWordWrap` | `true` | diff 自动换行 |
111
- | `diff.expandedPreviewMaxLines` | `40` | 展开后最多显示行数 |
112
-
113
- ## 兼容性
114
-
115
- 按 `VERIFIED_PI_VERSION = 1.0.2` 验收;版本不一致时扩展会在 session 启动时提示一次。
116
-
117
- ## 从源码安装
92
+ | `profile` ⟳ | `"claude"` | Overall look: claude = Claude Code-style views (default); native = pi native fallback. |
93
+ | `skillRendering` | `"native"` | Skill invocation block style: native = pi native [skill] collapsed line (default); claude = ● Skill(name) two-line style. |
94
+ | `subagentRendering` ⟳ | `"claude"` | Subagent completion notice style: claude = ● Subagent(desc) + branches (default); native = keep the bundled panel. |
95
+ | `detailsBackground` | `"selectedBg"` | Background color of the expanded tool details area; theme color key — `customMessageBg`/`toolPendingBg`/`toolSuccessBg`/`toolErrorBg` also work, `""` = none. |
96
+ | `bash/code/errorPreviewLines` | `0`/`0`/`0` | Preview lines for the three tool-result classes, 0–100 (0 = collapsed by default): bash output / read-write-edit-find tools / failed results. |
97
+ | `liveThinking` | `true` | Stream thinking blocks as they arrive. |
98
+ | `thinkingTailLines` | `5` | Lines kept at the tail when thinking is collapsed, ≥0. |
99
+ | `foldAnimMs` | `240` | Code block fold animation duration in milliseconds, ≥0 (0 disables it). |
100
+ | `stickyIncludeCustom` ⟳ | `false` | Whether the sticky bubble includes side-channel messages: CustomMessages injected by extensions or forwarded across sessions. |
101
+ | `skillResumePreview` ⟳ | `true` | Whether the /resume session list preview folds back to [skill] names. |
102
+ | `resultPrefix` | `"└"` | Corner prefix symbol for tool result lines (tab-drawing glyphs are the most reliable in monospace fonts; `↳` or Claude Code's official `⎿` also work, though both may widen in some fonts). |
103
+ | `resultPrefixGap` | `1` | Spaces between the prefix symbol and content, 0–4. |
104
+ | `normalizeForeignRenderers` | `true` | Rewrite the leading ⎿ of third-party extension output to resultPrefix (unifies the style of any unmatched extension's rendering; turn it off to see the third-party original). |
105
+ | `workflowRendering` ⟳ | `"claude"` | Workflow completion card style: claude = ● Workflow(name) (default); native = keep the bundled card. Dispatch-layer interception, unaffected by package load order. |
106
+ | `foreignCardStyle` | `"generic"` | Cards for unmatched customTypes: generic = take over as a unified `● card` (zero config); symbols = unify symbols only, keep layout. |
107
+ | `foreignCardSkip` | `[]` | customType blocklist never taken over (skip by type when an interactive third-party card misbehaves). |
108
+ | `diff.diffViewMode` | `"auto"` | Diff display mode: auto / split / unified. |
109
+ | `diff.diffIndicatorMode` | `"bars"` | Diff change indicator: bars / classic / none. |
110
+ | `diff.diffSplitMinWidth` | `120` | Minimum total width for split view; falls back to unified below it. |
111
+ | `diff.edit/writeDiffCollapsedLines` | `0`/`0` | Context lines kept when edit / write diffs are collapsed (0 = expanded by default). |
112
+ | `diff.diffWordWrap` | `true` | Wrap overly wide diff lines. |
113
+ | `diff.expandedPreviewMaxLines` | `40` | Maximum lines for expanded diff previews. |
114
+
115
+ ## Compatibility
116
+
117
+ Verified against `VERIFIED_PI_VERSION = 1.0.2`; on a version mismatch the extension shows a single notice at session startup.
118
+
119
+ ## Install from source
118
120
 
119
121
  ```bash
120
122
  pi install git:github.com/chenyn273/pi-claude-code-ui@v0.2.0
@@ -0,0 +1,123 @@
1
+ # @yinan_chen/pi-claude-code-ui
2
+
3
+ [English](README.md) | 简体中文
4
+
5
+ ![preview](assets/preview.png)
6
+
7
+ ### 演示
8
+
9
+ ![demo](https://github.com/user-attachments/assets/d264935a-79fc-434e-be97-05c8210144fe)
10
+
11
+ 动图预览(不渲染视频的平台,如 npm): ![gif](assets/demo.gif)
12
+
13
+ > 原名 lean-tidy —— Claude Code 风格的 Pi 界面增强扩展:Claude 式对话转录视图、富文本 diff 渲染、子代理面板与工作状态提示。
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ pi install npm:@yinan_chen/pi-claude-code-ui
19
+ ```
20
+
21
+ 试运行(不写入配置):
22
+
23
+ ```bash
24
+ pi -e npm:@yinan_chen/pi-claude-code-ui
25
+ ```
26
+
27
+ ## 功能
28
+
29
+ - **Claude 式视图** — 对话转录适配器,Pi 原生回退
30
+ - **吸附消息泡(sticky)** — 滚动时顶部吸附显示当前正在回答的用户消息原文,点击回位(v0.2.0 内置,原 sticky-first-message 扩展)
31
+ - **Skill 折叠(recollapse)** — 展开的 skill 全文自动折回 `/skill:名 参数`,覆盖 /tree、/fork 回填等四处盲区(v0.2.0 内置,原 skill-block-recollapse 扩展)
32
+ - **第三方渲染全接管** — 任何扩展的卡片(含未来未适配的)自动渲染为统一的 `● 卡片` 风格,工具/消息/条目三层全覆盖,零配置零适配;分发层拦截不受加载顺序影响(v0.7.0)
33
+ - **富 diff 渲染** — edit/write 工具的语法高亮 diff(vendored, MIT)
34
+ - **子代理面板** — 后台 agent 状态一目了然
35
+ - **工作状态提示** — working message / running effect
36
+ - **只读工具美化** — read/bash/grep/find/ls 结果渲染
37
+ - **设置面板** — `/skin-setting` 即改即存调整全部皮肤配置,`/skin-setting default` 一键恢复默认(v0.9.0)
38
+
39
+ ## 配置
40
+
41
+ ### 推荐方式:`/skin-setting` 设置面板
42
+
43
+ 在 pi 会话里输入 `/skin-setting` 打开居中 overlay 设置面板,即改即存——每次改动校验后立即写入配置文件并刷新面板行,没有"保存/放弃"负担:
44
+
45
+ - 内置搜索过滤,25 个配置键快速定位;每行附中文说明与默认值
46
+ - 枚举/布尔键回车在合法值间循环切换,不会选错;数字/字符串键回车进入输入框,带范围校验(如 0–100、0–4)
47
+ - 大多数键是热键,改动关闭面板后整屏重绘即刻可见;加载期固化的冷键常驻 `⟳ 需 /reload 生效` 标注,改动时会再提醒一次
48
+ - `foreignCardSkip` 黑名单只读展示,看得见状态又不会误编辑
49
+ - `/skin-setting default`:经确认对话框一键恢复全部默认(只清除本皮肤的配置键,文件里手写的其他内容保留)
50
+ - 非 TUI 模式(RPC / JSON / print)下运行会提示配置文件路径,请改用文件方式
51
+
52
+ 任何时刻可用 **F5** 整屏重绘(排残影的习惯操作)。
53
+
54
+ ### 手改配置文件
55
+
56
+ 也可以直接编辑 `~/.pi/agent/lean-tidy.json`(不存在时全部使用默认值)。手改后需 `/reload` 生效;设置面板的改动则是热键即时生效、冷键(下表标 `⟳` 者)需 `/reload`:
57
+
58
+ ```json
59
+ {
60
+ "profile": "claude",
61
+ "skillRendering": "native",
62
+ "subagentRendering": "claude",
63
+ "detailsBackground": "selectedBg",
64
+ "bashPreviewLines": 0,
65
+ "codePreviewLines": 0,
66
+ "errorPreviewLines": 0,
67
+ "liveThinking": true,
68
+ "thinkingTailLines": 5,
69
+ "foldAnimMs": 240,
70
+ "stickyIncludeCustom": false,
71
+ "skillResumePreview": true,
72
+ "resultPrefix": "└",
73
+ "resultPrefixGap": 1,
74
+ "normalizeForeignRenderers": true,
75
+ "workflowRendering": "claude",
76
+ "foreignCardStyle": "generic",
77
+ "foreignCardSkip": [],
78
+ "diff": {
79
+ "diffViewMode": "auto",
80
+ "diffIndicatorMode": "bars",
81
+ "diffSplitMinWidth": 120,
82
+ "editDiffCollapsedLines": 0,
83
+ "writeDiffCollapsedLines": 0,
84
+ "diffWordWrap": true,
85
+ "expandedPreviewMaxLines": 40
86
+ }
87
+ }
88
+ ```
89
+
90
+ | 键 | 默认 | 说明 |
91
+ |---|---|---|
92
+ | `profile` ⟳ | `"claude"` | 整体风格:`claude` / `native`(pi 原生) |
93
+ | `skillRendering` | `"native"` | skill 块样式:pi 原生折叠行 / `claude` 两行风格 |
94
+ | `subagentRendering` ⟳ | `"claude"` | 子代理完成通知:`claude` 分支风格 / `native` 保留包内面板 |
95
+ | `detailsBackground` | `"selectedBg"` | 详情背景色(主题色键名,可选 `customMessageBg`/`toolPendingBg`/`toolSuccessBg`/`toolErrorBg`/`""`) |
96
+ | `bash/code/errorPreviewLines` | `0`/`0`/`0` | 三类工具结果的预览行数(0–100;0 = 默认折叠) |
97
+ | `liveThinking` | `true` | 思考过程流式展示 |
98
+ | `thinkingTailLines` | `5` | 思考尾行数 |
99
+ | `foldAnimMs` | `240` | 折叠动画时长(ms,0 关闭) |
100
+ | `stickyIncludeCustom` ⟳ | `false` | 吸附泡是否包含扩展注入/跨会话转发的旁路消息 |
101
+ | `skillResumePreview` ⟳ | `true` | `/resume` 列表预览是否折回 `[skill] 名` 单行 |
102
+ | `resultPrefix` | `"└"` | 工具结果行拐角前缀(制表符族,等宽字体最稳;可换 `↳` 或 Claude Code 官方的 `⎿`,后两者在部分字体下会变宽) |
103
+ | `resultPrefixGap` | `1` | 前缀符号与内容之间的空格数(0–4) |
104
+ | `normalizeForeignRenderers` | `true` | 第三方扩展卡片输出里行首 `⎿` 自动规范化为 `resultPrefix`(任何未适配扩展的渲染都能统一风格;关闭可看第三方原版) |
105
+ | `workflowRendering` ⟳ | `"claude"` | workflow 完成卡片样式:`● Workflow(name)` 风格 / `native` 保留包内卡片。分发层拦截实现,不受包加载顺序影响 |
106
+ | `foreignCardStyle` | `"generic"` | 未适配扩展的卡片:自动接管为通用 `● 卡片`(零配置);`symbols` = 仅统一符号不接管布局 |
107
+ | `foreignCardSkip` | `[]` | 不接管的 customType 黑名单(交互型第三方卡片异常时按类型跳过) |
108
+ | `diff.diffViewMode` | `"auto"` | diff 视图:`auto`/`split`/`unified` |
109
+ | `diff.diffIndicatorMode` | `"bars"` | 变更指示条:`bars`/`classic`/`none` |
110
+ | `diff.diffSplitMinWidth` | `120` | 宽于此才用 split 视图 |
111
+ | `diff.edit/writeDiffCollapsedLines` | `0`/`0` | edit/write 默认折叠行数(0 = 默认展开) |
112
+ | `diff.diffWordWrap` | `true` | diff 自动换行 |
113
+ | `diff.expandedPreviewMaxLines` | `40` | 展开后最多显示行数 |
114
+
115
+ ## 兼容性
116
+
117
+ 按 `VERIFIED_PI_VERSION = 1.0.2` 验收;版本不一致时扩展会在 session 启动时提示一次。
118
+
119
+ ## 从源码安装
120
+
121
+ ```bash
122
+ pi install git:github.com/chenyn273/pi-claude-code-ui@v0.2.0
123
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yinan_chen/pi-claude-code-ui",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Claude Code-style UI for Pi: transcript view, rich diffs, subagent panels and working-message status.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,7 +20,8 @@
20
20
  ],
21
21
  "files": [
22
22
  "src",
23
- "README.md"
23
+ "README.md",
24
+ "README.zh-CN.md"
24
25
  ],
25
26
  "pi": {
26
27
  "extensions": [
package/src/index.ts CHANGED
@@ -571,7 +571,7 @@ export default async function leanTidy(pi: ExtensionAPI): Promise<void> {
571
571
  * 冷键在加载期固化(视图与面板注册),运行时 apply 无意义,直接跳过。 */
572
572
  function applySetting(id: string, value: unknown): void {
573
573
  const def = SETTING_BY_ID.get(id);
574
- if (!def || def.cold) return;
574
+ if (!def || def.reloadRequired) return;
575
575
  if (id.startsWith("diff.")) {
576
576
  diffDisplayConfig = { ...diffDisplayConfig, [id.slice("diff.".length)]: value };
577
577
  return;
@@ -586,18 +586,18 @@ export default async function leanTidy(pi: ExtensionAPI): Promise<void> {
586
586
  let confirmed = false;
587
587
  try {
588
588
  confirmed = await ctx?.ui?.confirm?.(
589
- "恢复默认设置",
590
- "将删除 lean-tidy.json 中的全部已知皮肤配置键(未知键保留),热键立即恢复默认。确定继续?",
589
+ "Restore default settings",
590
+ "This will delete all known skin config keys from lean-tidy.json (unknown keys are kept); live settings revert immediately. Continue?",
591
591
  );
592
592
  } catch { confirmed = false; }
593
593
  if (!confirmed) return; // Esc / 取消:文件与运行时零改动。
594
- const { coldReset } = resetConfigFile(CONFIG_PATH);
594
+ const { reloadRequiredReset } = resetConfigFile(CONFIG_PATH);
595
595
  // diff 显示配置先整体回出厂拷贝(文件 diff 里的未知键不留在运行时),
596
596
  // 再走统一的热键 apply 通道逐键重置(幂等,值相同)。
597
597
  diffDisplayConfig = { ...DEFAULT_TOOL_DISPLAY_CONFIG };
598
598
  applyRuntimeDefaults(applySetting);
599
599
  forceRedraw();
600
- if (coldReset.length > 0) ctx?.ui?.notify?.(`已恢复默认,${coldReset.length} 项需 /reload 生效`, "info");
600
+ if (reloadRequiredReset.length > 0) ctx?.ui?.notify?.(`Defaults restored — ${reloadRequiredReset.length} setting(s) need /reload`, "info");
601
601
  }
602
602
 
603
603
  /** 设置面板(工单 03):当前值读取统一走 currentValueOf——顶层键读共享 config,diff.* 读 diffDisplayConfig(闭包每次读模块变量,apply 整体替换后仍取最新值)。 */
@@ -613,9 +613,9 @@ export default async function leanTidy(pi: ExtensionAPI): Promise<void> {
613
613
  // /skin-setting:无参 = 打开设置面板(仅 TUI;守卫与非 TUI 提示在面板模块内),
614
614
  // default = 确认后一键恢复默认。命令补全只提示 default。
615
615
  pi.registerCommand("skin-setting", {
616
- description: "lean-tidy 皮肤设置(default = 恢复默认)",
616
+ description: "lean-tidy: skin settings (default = reset to defaults)",
617
617
  getArgumentCompletions(prefix: string) {
618
- const items = [{ value: "default", label: "default", description: "恢复全部默认(未知键保留)" }];
618
+ const items = [{ value: "default", label: "default", description: "Reset all settings to defaults (unknown keys are kept)" }];
619
619
  return items.filter((item) => item.value.startsWith(prefix));
620
620
  },
621
621
  handler: async (args: string, ctx: any) => {
@@ -630,14 +630,14 @@ export default async function leanTidy(pi: ExtensionAPI): Promise<void> {
630
630
  return;
631
631
  }
632
632
  ctx?.ui?.notify?.(
633
- `未知参数「${arg}」。用法:/skin-setting 打开设置面板(仅 TUI 模式);/skin-setting default 恢复默认。配置文件:${CONFIG_PATH}`,
633
+ `Unknown argument "${arg}". Usage: /skin-setting opens the settings panel (TUI only); /skin-setting default restores defaults. Config file: ${CONFIG_PATH}`,
634
634
  "info",
635
635
  );
636
636
  },
637
637
  });
638
638
 
639
639
  pi.registerShortcut("f5" as any, {
640
- description: "lean-tidy: 强制整屏重绘",
640
+ description: "lean-tidy: force full redraw",
641
641
  handler: () => { forceRedraw(); },
642
642
  });
643
643
 
package/src/settings.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 皮肤设置纯逻辑模块(零 TUI 依赖)。
3
3
  *
4
- * 职责:25 个配置键的元数据(规范 id / label / 中文描述 / 类型 / 合法值或范围 /
4
+ * 职责:25 个配置键的元数据(规范 id / label / 英文描述 / 类型 / 合法值或范围 /
5
5
  * 冷热分类 / 默认值)、编辑侧校验、编辑形态派生判定(循环域 / 输入子菜单——
6
6
  * def.type 的装配侧分派单点收敛于此,validateSetting 的 switch 是校验唯一实现点)、
7
7
  * lean-tidy.json 的 read-modify-write 原子写、恢复默认的文件语义,以及热键默认值的
@@ -25,9 +25,9 @@ export type SettingType = "enum" | "boolean" | "number" | "string" | "readonly-a
25
25
  export interface SettingDef {
26
26
  /** 规范 id:顶层键用 camelCase,diff 段用 "diff.xxx" 点路径(与 lean-tidy.json 键名一致)。 */
27
27
  id: string;
28
- /** 面板展示用的简短标签。 */
28
+ /** 面板展示用的简短英文标签。 */
29
29
  label: string;
30
- /** 中文描述(含默认值),面板行与文档共用。 */
30
+ /** 英文描述(含默认值),面板行与文档共用。 */
31
31
  description: string;
32
32
  type: SettingType;
33
33
  defaultValue: unknown;
@@ -39,128 +39,128 @@ export interface SettingDef {
39
39
  /** true = 严格整数(0–100 / 0–4 类);false = 非负且向下取整(≥0 类)。 */
40
40
  integer?: boolean;
41
41
  /** 冷键:加载期固化,改动只落文件,需 /reload 生效。 */
42
- cold: boolean;
42
+ reloadRequired: boolean;
43
43
  }
44
44
 
45
45
  /** 25 键元数据。默认值是唯一事实来源:loadConfig 经 topLevelDefaults() 消费,消除双源。 */
46
46
  export const SETTING_DEFS: readonly SettingDef[] = [
47
47
  // ---- 顶层 18 键 ----
48
48
  {
49
- id: "profile", label: "界面配置档案", type: "enum", values: ["claude", "native"], defaultValue: "claude", cold: true,
50
- description: "整体呈现:claude = Claude Code 风格视图(默认);native = pi 原生回退。",
49
+ id: "profile", label: "Profile", type: "enum", values: ["claude", "native"], defaultValue: "claude", reloadRequired: true,
50
+ description: "Overall look: claude = Claude Code-style views (default); native = pi native fallback.",
51
51
  },
52
52
  {
53
- id: "skillRendering", label: "Skill 渲染", type: "enum", values: ["native", "claude"], defaultValue: "native", cold: false,
54
- description: "Skill 调用块样式:native = pi 原生 [skill] 折叠行(默认);claude = ● Skill(name) 两行风格。",
53
+ id: "skillRendering", label: "Skill rendering", type: "enum", values: ["native", "claude"], defaultValue: "native", reloadRequired: false,
54
+ description: "Skill invocation block style: native = pi native [skill] collapsed line (default); claude = ● Skill(name) two-line style.",
55
55
  },
56
56
  {
57
- id: "subagentRendering", label: "Subagent 渲染", type: "enum", values: ["native", "claude"], defaultValue: "claude", cold: true,
58
- description: "Subagent 完成通知样式:claude = ● Subagent(desc) + 分支(默认);native = 保留包内面板。",
57
+ id: "subagentRendering", label: "Subagent rendering", type: "enum", values: ["native", "claude"], defaultValue: "claude", reloadRequired: true,
58
+ description: "Subagent completion notice style: claude = ● Subagent(desc) + branches (default); native = keep the bundled panel.",
59
59
  },
60
60
  {
61
- id: "detailsBackground", label: "详情背景色", type: "enum",
61
+ id: "detailsBackground", label: "Details background", type: "enum",
62
62
  values: ["selectedBg", "customMessageBg", "toolPendingBg", "toolSuccessBg", "toolErrorBg", ""],
63
- defaultValue: "selectedBg", cold: false,
64
- description: "工具详情展开区的背景色(默认 selectedBg;空字符串 = 无背景)。",
63
+ defaultValue: "selectedBg", reloadRequired: false,
64
+ description: "Background color of the expanded tool details area (default selectedBg; empty string = no background).",
65
65
  },
66
66
  {
67
- id: "bashPreviewLines", label: "Bash 预览行数", type: "number", min: 0, max: 100, integer: true, defaultValue: 0, cold: false,
68
- description: "bash 工具折叠态的输出预览行数,0–100(默认 0)。",
67
+ id: "bashPreviewLines", label: "Bash preview lines", type: "number", min: 0, max: 100, integer: true, defaultValue: 0, reloadRequired: false,
68
+ description: "Output preview lines for collapsed bash tool results, 0–100 (default 0).",
69
69
  },
70
70
  {
71
- id: "codePreviewLines", label: "代码工具预览行数", type: "number", min: 0, max: 100, integer: true, defaultValue: 0, cold: false,
72
- description: "read / write / edit / find 等折叠态预览行数,0–100(默认 0)。",
71
+ id: "codePreviewLines", label: "Code preview lines", type: "number", min: 0, max: 100, integer: true, defaultValue: 0, reloadRequired: false,
72
+ description: "Preview lines for collapsed read / write / edit / find tools, 0–100 (default 0).",
73
73
  },
74
74
  {
75
- id: "errorPreviewLines", label: "错误预览行数", type: "number", min: 0, max: 100, integer: true, defaultValue: 0, cold: false,
76
- description: "失败工具结果折叠态的预览行数,0–100(默认 0)。",
75
+ id: "errorPreviewLines", label: "Error preview lines", type: "number", min: 0, max: 100, integer: true, defaultValue: 0, reloadRequired: false,
76
+ description: "Preview lines for collapsed failed tool results, 0–100 (default 0).",
77
77
  },
78
78
  {
79
- id: "liveThinking", label: "实时思考流", type: "boolean", defaultValue: true, cold: false,
80
- description: "是否流式显示 thinking 块(默认 true)。",
79
+ id: "liveThinking", label: "Live thinking stream", type: "boolean", defaultValue: true, reloadRequired: false,
80
+ description: "Stream thinking blocks as they arrive (default true).",
81
81
  },
82
82
  {
83
- id: "thinkingTailLines", label: "思考尾部行数", type: "number", min: 0, defaultValue: 5, cold: false,
84
- description: "折叠 thinking 时尾部保留的行数,≥0(默认 5)。",
83
+ id: "thinkingTailLines", label: "Thinking tail lines", type: "number", min: 0, defaultValue: 5, reloadRequired: false,
84
+ description: "Lines kept at the tail when thinking is collapsed, ≥0 (default 5).",
85
85
  },
86
86
  {
87
- id: "foldAnimMs", label: "折叠动画时长", type: "number", min: 0, defaultValue: 240, cold: false,
88
- description: "代码块折叠动画时长,毫秒,≥0(默认 240)。",
87
+ id: "foldAnimMs", label: "Fold animation duration", type: "number", min: 0, defaultValue: 240, reloadRequired: false,
88
+ description: "Code block fold animation duration in milliseconds, ≥0 (default 240).",
89
89
  },
90
90
  {
91
- id: "stickyIncludeCustom", label: "sticky 含扩展消息", type: "boolean", defaultValue: false, cold: true,
92
- description: "sticky 吸附池是否包含扩展注入的 CustomMessage(默认 false)。",
91
+ id: "stickyIncludeCustom", label: "Sticky includes custom messages", type: "boolean", defaultValue: false, reloadRequired: true,
92
+ description: "Whether the sticky pool includes CustomMessages injected by extensions (default false).",
93
93
  },
94
94
  {
95
- id: "skillResumePreview", label: "resume 预览折回", type: "boolean", defaultValue: true, cold: true,
96
- description: "/resume 会话列表预览是否折回 [skill] 名(默认 true)。",
95
+ id: "skillResumePreview", label: "Resume preview fold-back", type: "boolean", defaultValue: true, reloadRequired: true,
96
+ description: "Whether the /resume session list preview folds back to [skill] names (default true).",
97
97
  },
98
98
  {
99
- id: "resultPrefix", label: "结果行前缀", type: "string", defaultValue: "└", cold: false,
100
- description: "工具结果行的拐角前缀符号(默认 └;可换 ↳ 或 ⎿)。",
99
+ id: "resultPrefix", label: "Result line prefix", type: "string", defaultValue: "└", reloadRequired: false,
100
+ description: "Corner prefix symbol for tool result lines (default └; can be ↳ or ⎿).",
101
101
  },
102
102
  {
103
- id: "resultPrefixGap", label: "前缀后空格", type: "number", min: 0, max: 4, integer: true, defaultValue: 1, cold: false,
104
- description: "前缀符号与内容之间的空格数,0–4(默认 1)。",
103
+ id: "resultPrefixGap", label: "Prefix gap", type: "number", min: 0, max: 4, integer: true, defaultValue: 1, reloadRequired: false,
104
+ description: "Spaces between the prefix symbol and content, 0–4 (default 1).",
105
105
  },
106
106
  {
107
- id: "normalizeForeignRenderers", label: "第三方输出规范化", type: "boolean", defaultValue: true, cold: false,
108
- description: "第三方扩展渲染输出的行首 ⎿ 是否统一为 resultPrefix(默认 true)。",
107
+ id: "normalizeForeignRenderers", label: "Normalize foreign renderers", type: "boolean", defaultValue: true, reloadRequired: false,
108
+ description: "Rewrite the leading ⎿ of third-party extension output to resultPrefix (default true).",
109
109
  },
110
110
  {
111
- id: "workflowRendering", label: "Workflow 渲染", type: "enum", values: ["claude", "native"], defaultValue: "claude", cold: true,
112
- description: "workflow 完成卡片样式:claude = ● Workflow(name)(默认);native = 保留包内卡片。",
111
+ id: "workflowRendering", label: "Workflow rendering", type: "enum", values: ["claude", "native"], defaultValue: "claude", reloadRequired: true,
112
+ description: "Workflow completion card style: claude = ● Workflow(name) (default); native = keep the bundled card.",
113
113
  },
114
114
  {
115
- id: "foreignCardStyle", label: "未适配卡片样式", type: "enum", values: ["generic", "symbols"], defaultValue: "generic", cold: false,
116
- description: "未适配 customType 的卡片:generic = 自动接管为通用卡片(默认);symbols = 仅统一符号不接管布局。",
115
+ id: "foreignCardStyle", label: "Foreign card style", type: "enum", values: ["generic", "symbols"], defaultValue: "generic", reloadRequired: false,
116
+ description: "Cards for unmatched customTypes: generic = take over as a generic card (default); symbols = unify symbols only, keep layout.",
117
117
  },
118
118
  {
119
- id: "foreignCardSkip", label: "卡片接管黑名单", type: "readonly-array", defaultValue: [], cold: false,
120
- description: "不接管的 customType 黑名单(只读;默认 [])。",
119
+ id: "foreignCardSkip", label: "Card takeover blocklist", type: "readonly-array", defaultValue: [], reloadRequired: false,
120
+ description: "customType blocklist never taken over (read-only; default []).",
121
121
  },
122
122
  // ---- diff.* 7 键(默认值直接引用 vendored DEFAULT_TOOL_DISPLAY_CONFIG,保持同源)----
123
123
  {
124
- id: "diff.diffViewMode", label: "diff 视图模式", type: "enum", values: ["auto", "split", "unified"],
125
- defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.diffViewMode, cold: false,
126
- description: "diff 展示模式:auto / split / unified(默认 auto)。",
124
+ id: "diff.diffViewMode", label: "Diff view mode", type: "enum", values: ["auto", "split", "unified"],
125
+ defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.diffViewMode, reloadRequired: false,
126
+ description: "Diff display mode: auto / split / unified (default auto).",
127
127
  },
128
128
  {
129
- id: "diff.diffIndicatorMode", label: "diff 指示器", type: "enum", values: ["bars", "classic", "none"],
130
- defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.diffIndicatorMode, cold: false,
131
- description: "diff 变更指示器:bars / classic / none(默认 bars)。",
129
+ id: "diff.diffIndicatorMode", label: "Diff indicator", type: "enum", values: ["bars", "classic", "none"],
130
+ defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.diffIndicatorMode, reloadRequired: false,
131
+ description: "Diff change indicator: bars / classic / none (default bars).",
132
132
  },
133
133
  {
134
- id: "diff.diffSplitMinWidth", label: "split 最小宽度", type: "number", min: 0,
135
- defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.diffSplitMinWidth, cold: false,
136
- description: "split 视图的最小总宽度,低于则回落 unified(默认 120)。",
134
+ id: "diff.diffSplitMinWidth", label: "Split min width", type: "number", min: 0,
135
+ defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.diffSplitMinWidth, reloadRequired: false,
136
+ description: "Minimum total width for split view; falls back to unified below it (default 120).",
137
137
  },
138
138
  {
139
- id: "diff.editDiffCollapsedLines", label: "edit 折叠行数", type: "number", min: 0,
140
- defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.editDiffCollapsedLines, cold: false,
141
- description: "edit diff 折叠时显示的上下文行数(默认 0)。",
139
+ id: "diff.editDiffCollapsedLines", label: "Edit collapsed lines", type: "number", min: 0,
140
+ defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.editDiffCollapsedLines, reloadRequired: false,
141
+ description: "Context lines shown when an edit diff is collapsed (default 0).",
142
142
  },
143
143
  {
144
- id: "diff.writeDiffCollapsedLines", label: "write 折叠行数", type: "number", min: 0,
145
- defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.writeDiffCollapsedLines, cold: false,
146
- description: "write diff 折叠时显示的上下文行数(默认 0)。",
144
+ id: "diff.writeDiffCollapsedLines", label: "Write collapsed lines", type: "number", min: 0,
145
+ defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.writeDiffCollapsedLines, reloadRequired: false,
146
+ description: "Context lines shown when a write diff is collapsed (default 0).",
147
147
  },
148
148
  {
149
- id: "diff.diffWordWrap", label: "diff 自动换行", type: "boolean",
150
- defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.diffWordWrap, cold: false,
151
- description: "diff 行超宽时是否自动换行(默认 true)。",
149
+ id: "diff.diffWordWrap", label: "Diff word wrap", type: "boolean",
150
+ defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.diffWordWrap, reloadRequired: false,
151
+ description: "Wrap overly wide diff lines (default true).",
152
152
  },
153
153
  {
154
- id: "diff.expandedPreviewMaxLines", label: "展开预览行上限", type: "number", min: 0,
155
- defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines, cold: false,
156
- description: "diff 展开态预览的最大行数(默认 40)。",
154
+ id: "diff.expandedPreviewMaxLines", label: "Expanded preview max lines", type: "number", min: 0,
155
+ defaultValue: DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines, reloadRequired: false,
156
+ description: "Maximum lines for expanded diff previews (default 40).",
157
157
  },
158
158
  ];
159
159
 
160
160
  export const SETTING_BY_ID: ReadonlyMap<string, SettingDef> = new Map(SETTING_DEFS.map((def) => [def.id, def]));
161
161
 
162
162
  /** 冷键 id 清单(派生自元数据):加载期固化,运行时不可 apply。 */
163
- export const COLD_SETTING_IDS: readonly string[] = SETTING_DEFS.filter((def) => def.cold).map((def) => def.id);
163
+ export const RELOAD_REQUIRED_SETTING_IDS: readonly string[] = SETTING_DEFS.filter((def) => def.reloadRequired).map((def) => def.id);
164
164
 
165
165
  /** 18 个顶层键的默认值对象——loadConfig 的初始值由此构造(默认值单一事实来源)。 */
166
166
  export function topLevelDefaults(): Record<string, unknown> {
@@ -194,39 +194,39 @@ export function hasInputEditor(def: SettingDef): boolean {
194
194
 
195
195
  export type ValidateResult = { ok: true; value: unknown } | { ok: false; error: string };
196
196
 
197
- /** 校验单键取值:非法值拒绝并给出中文错误;通过时返回归一化后的值(≥0 键取整)。 */
197
+ /** 校验单键取值:非法值拒绝并给出英文错误;通过时返回归一化后的值(≥0 键取整)。 */
198
198
  export function validateSetting(id: string, value: unknown): ValidateResult {
199
199
  const def = SETTING_BY_ID.get(id);
200
- if (!def) return { ok: false, error: `未知配置键:${id}` };
200
+ if (!def) return { ok: false, error: `Unknown setting key: ${id}` };
201
201
  switch (def.type) {
202
202
  case "enum": {
203
203
  if (def.values?.includes(value)) return { ok: true, value };
204
204
  const legal = def.values?.map((v) => JSON.stringify(v)).join(" / ") ?? "";
205
- return { ok: false, error: `${def.label} 只接受 ${legal} 之一` };
205
+ return { ok: false, error: `${def.label} only accepts one of ${legal}` };
206
206
  }
207
207
  case "boolean":
208
208
  return typeof value === "boolean"
209
209
  ? { ok: true, value }
210
- : { ok: false, error: `${def.label} 只接受 true / false` };
210
+ : { ok: false, error: `${def.label} only accepts true / false` };
211
211
  case "number": {
212
212
  if (typeof value !== "number" || !Number.isFinite(value)) {
213
- return { ok: false, error: `${def.label} 需要是数字` };
213
+ return { ok: false, error: `${def.label} must be a number` };
214
214
  }
215
215
  if (def.integer && !Number.isInteger(value)) {
216
- return { ok: false, error: `${def.label} 需要是整数(${def.min}–${def.max})` };
216
+ return { ok: false, error: `${def.label} must be an integer (${def.min}–${def.max})` };
217
217
  }
218
218
  if (value < (def.min ?? -Infinity) || value > (def.max ?? Infinity)) {
219
219
  const upper = def.max === undefined ? "∞" : def.max;
220
- return { ok: false, error: `${def.label} 超出范围 ${def.min}–${upper}` };
220
+ return { ok: false, error: `${def.label} is out of range ${def.min}–${upper}` };
221
221
  }
222
222
  return { ok: true, value: def.integer ? value : Math.floor(value) };
223
223
  }
224
224
  case "string":
225
225
  return typeof value === "string" && value.trim()
226
226
  ? { ok: true, value }
227
- : { ok: false, error: `${def.label} 不能为空白字符串` };
227
+ : { ok: false, error: `${def.label} cannot be a blank string` };
228
228
  case "readonly-array":
229
- return { ok: false, error: `${def.label} 为只读黑名单,不支持在此编辑(请手改配置文件)` };
229
+ return { ok: false, error: `${def.label} is a read-only blocklist; edit the config file manually` };
230
230
  }
231
231
  }
232
232
 
@@ -284,7 +284,7 @@ export function setSettingInFile(path: string, id: string, value: unknown): Reco
284
284
 
285
285
  export interface ResetFileResult {
286
286
  /** 文件中原本非默认、被本次删除的冷键 id(调用方据此提醒「N 项需 /reload 生效」)。 */
287
- coldReset: string[];
287
+ reloadRequiredReset: string[];
288
288
  }
289
289
 
290
290
  /**
@@ -293,13 +293,13 @@ export interface ResetFileResult {
293
293
  * 冷键默认值均为原始类型(enum/boolean),用严格相等判断「原本非默认」。
294
294
  */
295
295
  export function resetConfigFile(path: string): ResetFileResult {
296
- if (!existsSync(path)) return { coldReset: [] };
296
+ if (!existsSync(path)) return { reloadRequiredReset: [] };
297
297
  const obj = readConfigObject(path);
298
- const coldReset: string[] = [];
298
+ const reloadRequiredReset: string[] = [];
299
299
  for (const def of SETTING_DEFS) {
300
300
  if (def.id.includes(".")) continue;
301
301
  if (!(def.id in obj)) continue;
302
- if (def.cold && obj[def.id] !== def.defaultValue) coldReset.push(def.id);
302
+ if (def.reloadRequired && obj[def.id] !== def.defaultValue) reloadRequiredReset.push(def.id);
303
303
  delete obj[def.id];
304
304
  }
305
305
  const diff = obj.diff;
@@ -311,7 +311,7 @@ export function resetConfigFile(path: string): ResetFileResult {
311
311
  if (Object.keys(diff).length === 0) delete obj.diff;
312
312
  }
313
313
  writeConfigAtomic(path, obj);
314
- return { coldReset };
314
+ return { reloadRequiredReset };
315
315
  }
316
316
 
317
317
  /* ---------- 运行时重置(回调注入) ---------- */
@@ -322,6 +322,6 @@ export type ApplyFn = (id: string, value: unknown) => void;
322
322
  /** 热键逐键 apply 默认值;冷键加载期固化、不 apply(/reload 后生效)。 */
323
323
  export function applyRuntimeDefaults(apply: ApplyFn): void {
324
324
  for (const def of SETTING_DEFS) {
325
- if (!def.cold) apply(def.id, def.defaultValue);
325
+ if (!def.reloadRequired) apply(def.id, def.defaultValue);
326
326
  }
327
327
  }
@@ -60,7 +60,7 @@ function themeFg(theme: any): (color: string, text: string) => string {
60
60
  /** SettingsList 的子菜单 done 契约(settings-list.d.ts):带值提交 → 行刷新 + onChange;无值 → 仅关闭回原行。 */
61
61
  type SubmenuDone = (selectedValue?: string, options?: { navigateTo?: string }) => void;
62
62
 
63
- /** 即改即存提交链路结果:失败时携带中文错误(由调用方决定 notify 还是子菜单原地展示)。 */
63
+ /** 即改即存提交链路结果:失败时携带英文错误(由调用方决定 notify 还是子菜单原地展示)。 */
64
64
  export type CommitResult = { ok: true; value: unknown } | { ok: false; error: string };
65
65
  export type CommitFn = (id: string, value: unknown) => CommitResult;
66
66
 
@@ -68,9 +68,9 @@ export type CommitFn = (id: string, value: unknown) => CommitResult;
68
68
  function inputConstraintHint(def: SettingDef): string {
69
69
  if (def.type === "number") {
70
70
  const upper = def.max === undefined ? "∞" : String(def.max);
71
- return def.integer ? `范围 ${def.min}–${upper}(整数)` : `范围 ≥ ${def.min}(小数自动向下取整)`;
71
+ return def.integer ? `Range ${def.min}–${upper} (integer)` : `Range ≥ ${def.min} (fractional values are floored)`;
72
72
  }
73
- return "trim 后非空";
73
+ return "non-blank after trim";
74
74
  }
75
75
 
76
76
  /** 文本 → 待校验值:数字键 Number() 解析(空输入按 NaN,交给 validateSetting 出中文错误);字符串键原样。 */
@@ -80,7 +80,7 @@ function parseInputValue(def: SettingDef, text: string): unknown {
80
80
  return trimmed === "" ? Number.NaN : Number(trimmed);
81
81
  }
82
82
 
83
- /** 原地错误行:无错误时零行,有错误时一行红色中文提示(非法输入不落盘的呈现位)。 */
83
+ /** 原地错误行:无错误时零行,有错误时一行红色提示(非法输入不落盘的呈现位)。 */
84
84
  class SubmenuErrorLine implements Component {
85
85
  error: string | null = null;
86
86
  private readonly fg: (color: string, text: string) => string;
@@ -129,14 +129,14 @@ export class SettingInputSubmenu extends Container {
129
129
  }
130
130
  const placeholder = def.type === "number" ? String(current) : String(current ?? "");
131
131
  this.addChild(new Text(fg("accent", def.label), 1, 0));
132
- this.addChild(new Text(fg("muted", `${inputConstraintHint(def)} · 当前 ${currentValueJson}`), 1, 0));
132
+ this.addChild(new Text(fg("muted", `${inputConstraintHint(def)} · current ${currentValueJson}`), 1, 0));
133
133
  this.addChild(new Spacer(1));
134
134
  this.input = new Input({ placeholder, placeholderStyle: (text: string) => fg("dim", text) });
135
135
  this.errorLine = new SubmenuErrorLine(fg);
136
136
  this.input.onSubmit = (text: string) => {
137
137
  const result = commit(def.id, parseInputValue(def, text));
138
138
  if (!result.ok) {
139
- // 原地提示不落盘:子菜单里显示中文错误行,输入框保留用户输入供改正,不关子菜单。
139
+ // 原地提示不落盘:子菜单里显示错误行,输入框保留用户输入供改正,不关子菜单。
140
140
  this.errorLine.error = result.error;
141
141
  return;
142
142
  }
@@ -148,7 +148,7 @@ export class SettingInputSubmenu extends Container {
148
148
  };
149
149
  this.addChild(this.input);
150
150
  this.addChild(this.errorLine);
151
- this.addChild(new Text(fg("dim", "Enter 保存 · Esc 取消"), 1, 0));
151
+ this.addChild(new Text(fg("dim", "Enter to save · Esc to cancel"), 1, 0));
152
152
  }
153
153
 
154
154
  handleInput(data: string): void {
@@ -161,14 +161,14 @@ export class SettingInputSubmenu extends Container {
161
161
  /**
162
162
  * 面板行装配:25 键 → SettingItem[]。
163
163
  * 冷键标注双保险:label 尾部 ⟳(SettingsList 的 description 只在选中行显示,
164
- * 要「常驻可见」必须落在 label 上)+ description 尾部(⟳ 需 /reload 生效)。
164
+ * 要「常驻可见」必须落在 label 上)+ description 尾部 (⟳ needs /reload)。
165
165
  */
166
166
  export function buildSettingItems(currentValueOf: (id: string) => unknown): SettingItem[] {
167
167
  return SETTING_DEFS.map((def) => {
168
168
  const item: SettingItem = {
169
169
  id: def.id,
170
- label: def.cold ? `${def.label} ⟳` : def.label,
171
- description: def.cold ? `${def.description}(⟳ 需 /reload 生效)` : def.description,
170
+ label: def.reloadRequired ? `${def.label} ⟳` : def.label,
171
+ description: def.reloadRequired ? `${def.description} (⟳ needs /reload)` : def.description,
172
172
  currentValue: toJsonLiteral(currentValueOf(def.id)),
173
173
  };
174
174
  // 循环域(enum/boolean)由 settings.ts 的 cycleValuesOf 单点派生;number/string 的输入
@@ -209,10 +209,10 @@ class BorderLine implements Component {
209
209
  }
210
210
  }
211
211
 
212
- /** 标题边框行:── 皮肤设置(…) ──,标题居中、随渲染宽度自适应。 */
212
+ /** 标题边框行:── Skin Settings(…) ──,标题居中、随渲染宽度自适应(副句表达即改即存与输入即搜索)。 */
213
213
  function titleBorder(fg: (color: string, text: string) => string): BorderLine {
214
214
  return new BorderLine((width) => {
215
- const title = " 皮肤设置(回车即改即存 · 输入即搜索) ";
215
+ const title = " Skin Settings (auto-save on change · type to search) ";
216
216
  const fill = width - displayWidth(title) - 2;
217
217
  const left = Math.max(1, Math.floor(fill / 2));
218
218
  const right = Math.max(1, fill - left);
@@ -273,10 +273,10 @@ export async function handleSkinSettingCommand(deps: SkinSettingPanelDeps, ctx:
273
273
  // 冷键 notify 去抖:同一键同一次面板会话只在首次改动时提醒。
274
274
  // 理由:回车循环极易连按(切过头再切回),每按一次弹一条会刷屏遮挡面板;
275
275
  // 首次提醒已传达「此键需 /reload」,同键后续改动无新信息。重开面板重置(新意图)。
276
- const coldNotified = new Set<string>();
276
+ const reloadRequiredNotified = new Set<string>();
277
277
  let panel: SkinSettingPanelComponent;
278
278
  // 即改即存提交链路(enum 循环与子菜单输入共用):校验 → RMW 落盘 → 热键原地
279
- // 生效 → 面板行刷新 → 冷键首次 notify。失败返回中文错误,由调用方决定呈现
279
+ // 生效 → 面板行刷新 → 冷键首次 notify。失败返回英文错误,由调用方决定呈现
280
280
  // (列表循环路径 notify + 行回滚;子菜单路径原地错误行,不关子菜单)。
281
281
  const commit: CommitFn = (id, value) => {
282
282
  // 「未知配置键」错误文案由 validateSetting 单点产出(原先此处的预查是其重复);
@@ -287,13 +287,13 @@ export async function handleSkinSettingCommand(deps: SkinSettingPanelDeps, ctx:
287
287
  try {
288
288
  setSettingInFile(deps.configPath, id, check.value);
289
289
  } catch (error: any) {
290
- return { ok: false, error: `写入 ${deps.configPath} 失败:${error?.message ?? error}` };
290
+ return { ok: false, error: `Failed to write ${deps.configPath}: ${error?.message ?? error}` };
291
291
  }
292
292
  deps.applySetting(id, check.value); // 热键原地生效;冷键 write-only(注入方 no-op)
293
293
  panel.list.updateValue(id, toJsonLiteral(check.value));
294
- if (def.cold && !coldNotified.has(id)) {
295
- coldNotified.add(id);
296
- notify(`⟳ ${def.label} 已写入,/reload 后生效`, "info");
294
+ if (def.reloadRequired && !reloadRequiredNotified.has(id)) {
295
+ reloadRequiredNotified.add(id);
296
+ notify(`⟳ ${def.label} saved — takes effect after /reload`, "info");
297
297
  }
298
298
  return { ok: true, value: check.value };
299
299
  };
@@ -337,7 +337,7 @@ export async function handleSkinSettingCommand(deps: SkinSettingPanelDeps, ctx:
337
337
  ctx?.ui?.notify?.(message, level);
338
338
  };
339
339
  if (ctx?.mode !== "tui" || typeof ctx?.ui?.custom !== "function") {
340
- notify(`用法:/skin-setting 打开设置面板(仅 TUI 模式);/skin-setting default 恢复默认。配置文件:${deps.configPath}`, "info");
340
+ notify(`Usage: /skin-setting opens the settings panel (TUI only); /skin-setting default restores defaults. Config file: ${deps.configPath}`, "info");
341
341
  return;
342
342
  }
343
343
  await ctx.ui.custom(