Files
claude-code/docs/superpowers/specs/2026-06-14-effort-panel-design.md
claude-code-best 96cc805ae5 docs(effort): 新增 /effort 交互面板设计 spec
设计要点:
- /effort 无参 → 横向 slider 面板(low/medium/high/xhigh/max/ultracode)
- ←/→ 移动光标,Enter 确认,Esc 取消
- ultracode 仅视觉占位,确认后提示走 /ultracode <context>
- env override 时双标记 + 顶部警告
- 模型不支持时面板禁用
- 两阶段交付:先基础面板 commit,再做 ultracode 波纹动画

Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
2026-06-14 14:11:21 +08:00

16 KiB
Raw Blame History

Effort 交互面板EffortPanel设计

日期: 2026-06-14 作者: brainstorming session 产物 状态: 待实施 关联: src/commands/effort/src/utils/effort.tssrc/components/EffortPanel/(新增)


1. 概述

把当前的 /effort slash 命令从纯文本式交互升级为终端内的可视化选择面板。

  • 触发:/effort(无参)打开面板;/effort <level> 直跳路径保留
  • 视觉:横向 slider两端标 Faster / Smarter,刻度为 low / medium / high / xhigh / max / ultracode
  • 交互:←/→ 移动光标,Enter 确认,Esc 取消
  • ultracode 仅作视觉占位,确认后提示用户走 /ultracode <context> 启动
  • 第二阶段加波纹动画(详见 §6

2. 用户故事

  • 作为开发者,我希望按 /effort 就能可视化地选择努力等级,而不用记 5 个枚举值
  • 作为高频用户,我希望 /effort high 这种直跳仍可用,避免脚本/习惯被打断
  • 作为设置了 CLAUDE_CODE_EFFORT_LEVEL 的用户,我希望面板提示我"env 优先级更高",而不是默默忽略我的选择
  • 作为想试 ultracode 的用户,我希望面板让我知道这个"档位"存在,但落地要走它自己的命令

3. 不在本期范围

  • 不修改 EffortValue / EffortLevel 类型
  • 不修改 src/utils/effort.ts 的任何纯函数
  • 不新增专用全局热键(仅通过 /effort 触发)
  • 不在面板里包含 auto 选项(仍走 /effort auto
  • 不真正"启用 ultracode"——面板对 ultracode 仅作视觉提示与文案引导

4. 架构与文件结构

src/
├── commands/effort/
│   ├── effort.tsx              ← 改造call() 在 args 为空时返回 <EffortPanel>
│   │                              有参时维持原 executeEffort() 路径
│   └── index.ts                ← 不变
├── components/EffortPanel/
│   ├── EffortPanel.tsx         ← 新增:面板主体(渲染 + 键盘交互 + onDone 通道)
│   ├── effortPanelState.ts     ← 新增:纯函数 reducer移动光标、确定选项
│   │                              抽离便于单测
│   └── __tests__/
│       ├── EffortPanel.test.tsx        ← 渲染 / 键盘交互 / env 警告 / ultracode 提示
│       └── effortPanelState.test.ts    ← reducer 纯函数测试

复用清单(不重写)

  • executeEffort() / setEffortValue() / unsetEffortLevel():留在 effort.tsx,面板确认时调用
  • EFFORT_LEVELS / getDisplayedEffortLevel() / getEffortEnvOverride() / getEffortValueDescription() / modelSupportsEffort():从 src/utils/effort.ts 直接 import
  • useInputuseKeyboard:从 @anthropic/ink
  • <ApplyEffortAndClose> 组件:作为面板 Enter 后的"写入并退出"流程组件复用(或迁入 EffortPanel 内部)

类型层面

不动 EffortValue / EffortLevel。面板内部用一个新类型 PanelPosition 表示光标位置:

type PanelPosition = 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'ultracode';

它仅在面板内部使用,不进入 AppState、不进入 settings.json、不参与 API 调用。

5. 交互流程

触发与初始光标

/effort<回车>(无参)
  → call() 检测 args === ''
  → 渲染 <EffortPanel onDone={onDone} appStateEffort={effortValue} model={model} />
  → 光标初始位置:
       env override 存在时 → env 设定的档位(让用户立刻看到生效值)
       否则 → getDisplayedEffortLevel(model, appStateEffort)

状态机

状态:{ cursor: PanelPosition }

事件:
  ← (ArrowLeft)    → cursor 左移一位low 处不左移,保持 low
  → (ArrowRight)   → cursor 右移一位ultracode 处不右移,保持 ultracode
  Home / h         → cursor = low
  End / l          → cursor = ultracode
  Enter            → 确认分支(见下)
  Esc / Ctrl+C / q → 取消onDone("Effort unchanged.")

确认后的两条分支

分支 Acursor ∈ {low, medium, high, xhigh, max}

调 executeEffort(cursor)
  → setEffortValue 写 settings + AppState
  → 拿到 result.message
onDone(result.message)

(与现有 /effort high 完全一致的消息体例,含 env override 警告)

分支 Bcursor === 'ultracode'

不调 executeEffort
onDone("ultracode 不是 effort 档位。请使用 /ultracode <context> 启动多 agent workflow。")

取消路径

不调 executeEffort、不写 AppState、不写 settings。onDone("Effort unchanged.")

不变路径(仍走原 effort.tsx 逻辑)

  • /effort low|medium|high|xhigh|max:直跳
  • /effort auto|unsetunsetEffortLevel
  • /effort help|-h|--helphelp 文本
  • /effort current|statusShowCurrentEffort

焦点与键盘独占

面板挂载时通过 Ink useInput 抢占键盘;卸载时自动释放(与 AskUserQuestionPermissionRequest 一致)。

6. 视觉布局

基本形态(无 env override

Effort

       Faster                                                          Smarter
       ─────────────────────────▲──────────────────────────────────────────────
       low        medium       high       xhigh        max        ultracode
                                                      xhigh + workflows

       ←/→ adjust · Enter confirm · Esc cancel

视觉规则

元素 规则
光标 跟随 cursor 状态移动,永远指向当前 cursor 位置
当前生效档位active 当 cursor ≠ active 时active 档渲染为加粗 + 旁标 (active);当 cursor === active 时只显示 ,避免双标记
ultracode 副标签 固定字符串 xhigh + workflowsdim 色
两极文字 Faster / Smarter 与面板等宽左右对齐;中间用一行 填充
底栏提示 ←/→ adjust · Enter confirm · Esc canceldim 色
标题 Effort 加粗,居中或左对齐

双标记渲染cursor ≠ active

env override 时会出现,例如:

Effort
⚠ CLAUDE_CODE_EFFORT_LEVEL=high overrides this session

       Faster                                                          Smarter
       ────────────────────────▲────────────────────────▲──────────────────────
       low        medium      (high) active   xhigh        max        ultracode
                                                      xhigh + workflows

       ←/→ adjust · Enter confirm · Esc cancel
  • 上方cursor 位置xhigh
  • (high) activeenv 锁定的真实生效档位

两个标记视觉上必须区分cursor 用三角符号active 用括号文字 + 颜色。

模型不支持 effort 时(modelSupportsEffort(model) === false

Effort

  当前模型 <model> 不支持 effort 参数。面板已禁用。

       Faster                                                          Smarter
       ────────────────────────────────────────────────────────────────────────
       low        medium       high       xhigh        max        ultracode

       Esc to close

光标不显示左右键无效Enter 无效,只能 Esc 退出。

终端窄屏(< 60 cols适配

简化策略:宽度 < 60 时退化为垂直列表,每档一行;否则保持横向 slider。这一项不阻塞首版,先按横向渲染,必要时溢出,后续看实际效果再调。

7. 背景波纹动画(第二阶段,单独 commit

触发条件

仅在 cursor 停在 ultracode 时启动波纹;移开时立即停止(不淡出,干脆)。常态零干扰。

视觉概念

ultracode 是面板的"能量溢出口"。波纹从 ultracode 字符位置(右下区域)为震源,向左/向上辐射同心圆波,铺满整个面板的留白区域(文字字符之间的空隙、 分隔线的空白段)。文字层永远清晰可读。

字符集(强度 → 字符)

强度 字符
0.0 (空格)
0.1 ·
0.3
0.5
0.7
0.9
波峰 ~ 循环

波纹数学

对每个字符格:
  dx = x - sourceX
  dy = (y - sourceY) * 1.5
  dist = sqrt(dx*dx + dy*dy)
  
  phase = dist * 0.4 - time * 0.012
  wave = sin(phase)
  falloff = max(0, 1 - dist / 40)
  intensity = max(0, wave) * falloff
  
  if (dist < 6):    // 震源附近高频涟漪
    intensity = max(intensity, 0.5 + 0.5 * sin(time * 0.02 - dist * 1.2))
  
  char = pick(intensity)

参数上线后调。

渲染策略(双层不冲突)

Ink 不支持真正的 z-index 层叠,用字符替换模拟:

  1. 每帧生成 height × width 字符矩阵(背景层)
  2. 渲染每个面板行时,先取该行对应的波纹字符序列,然后在文字字符应该出现的位置覆盖背景字符
  3. 文字字符永远胜出,波纹只占空隙

实现位置

新增(第二阶段):

  • src/components/EffortPanel/rippleAnimation.tspickChar / computeRippleLine / mergeLayers 纯函数
  • src/components/EffortPanel/useRippleFrame.ts — hook内部调 useAnimationFrame(60) 返回当前帧矩阵
  • EffortPanel.tsx 的 render 中叠加(仅 cursor === 'ultracode' 时启用)

性能预算

  • 面板 80×10 = 800 格,每帧 800 次 sin/sqrt ≈ 0.05ms
  • Ink 重绘 10 行 <Text> 节点,与现有 Spinner 同量级
  • 帧率 16fpsuseAnimationFrame 自带 viewport 不可见暂停 + 失焦减速

风险与对策

风险 对策
波纹干扰文字可读性 文字字符覆盖背景字符,永远胜出;波纹颜色用 theme.textDisabled
终端窄屏 < 60 cols sourceX 跟随 ultracode 实际位置;窄屏时降级为单行波纹
性能(旧机器) useAnimationFrame 已自带暂停/减速
测试稳定性 字符选择是纯函数,可固定 time 注入做帧快照测试

8. 数据流

状态来源

┌─────────────────────────────────────────────────┐
│ src/state/AppState.tsx                          │
│   effortValue: EffortValue | undefined          │
└─────────────────────────────────────────────────┘
              ▲
              │ useAppState(s => s.effortValue)
              │
┌─────────────────────────────────────────────────┐
│ EffortPanel.tsx                                 │
│   props: appStateEffort, model, onDone          │
│   local: cursor: PanelPosition                  │
└─────────────────────────────────────────────────┘
              │
              │ Enter 确认
              ▼
┌─────────────────────────────────────────────────┐
│ executeEffort(cursor)                           │
│   → updateSettingsForSource('userSettings', …)  │
│   → logEvent('tengu_effort_command', …)         │
│   → 返回 { message, effortUpdate? }             │
└─────────────────────────────────────────────────┘
              │
              │ <ApplyEffortAndClose> setAppState(...)
              ▼
┌─────────────────────────────────────────────────┐
│ onDone(result.message)                          │
│   → REPL 渲染 assistant 消息                    │
└─────────────────────────────────────────────────┘

优先级链(不修改)

env CLAUDE_CODE_EFFORT_LEVEL  >  AppState.effortValue  >  model default

面板只写 AppState + settings.json不直接操作 env。env 存在时,面板可操作但顶部警告(详见 §6 双标记)。

9. 边界与错误处理

场景 行为
模型不支持 effort 面板挂载但禁用,文字提示 + 仅允许 Esc详见 §6
env override 设定 顶部加黄色警告行 ⚠ CLAUDE_CODE_EFFORT_LEVEL=<value> overrides this session光标可移动Enter 仍写 settings 但顶部警告解释生效值不变
cursor === 'ultracode' 时 Enter 走分支 B输出引导文案不调 executeEffort
settings 写入失败(磁盘满/权限) executeEffort 现有错误路径会返回 result.error面板沿用onDone 输出错误消息
终端窄屏 < 60 cols 退化为垂直列表,不阻塞首版
用户按 Ctrl+C 之外的中断信号 视同 EsconDone("Effort unchanged.")
面板挂载后 AppState 被外部改变(如 /model 切换) cursor 不订阅 active 变化,挂载时计算一次初始值后只跟随用户操作。若用户切了 model 想看新档位,关掉面板重开即可。简化实现,行为可预测

10. 测试计划

纯函数(effortPanelState.test.ts

  • moveLeft(cursor) 在 low 处保持 low
  • moveRight(cursor) 在 ultracode 处保持 ultracode
  • home(cursor) / end(cursor) 边界
  • getInitialCursor(appStateEffort, envOverride, model) 优先级
  • isUltracode(cursor) 守卫

组件(EffortPanel.test.tsx

渲染:

  • 无 env 时显示基本形态
  • env override 时顶部警告 + 双标记
  • 模型不支持时禁用面板
  • ultracode 副标签 xhigh + workflows 出现

键盘:

  • 移动光标、 移动光标、Home/End 跳转
  • Enter 在普通档位 → 调用 executeEffort、onDone 收到正确 message
  • Enter 在 ultracode → 不调 executeEffort、onDone 收到引导文案
  • Esc → 不调 executeEffort、onDone 收到 "Effort unchanged."

集成(effort.tsx 的 call 函数):

  • 无参 → 返回 <EffortPanel> JSX
  • 有参 → 不渲染面板,走 executeEffort

波纹相关(第二阶段)

  • pickChar(intensity) 各强度边界
  • computeRippleLine 固定 time 快照
  • mergeLayers 文字覆盖背景、文字字符永远胜出
  • useRippleFrame 仅在 cursor === 'ultracode' 时订阅时钟

11. 实现阶段划分(两个 commit

Commit 1基础面板先做

  • 新增 src/components/EffortPanel/EffortPanel.tsx
  • 新增 src/components/EffortPanel/effortPanelState.ts
  • 新增 src/components/EffortPanel/__tests__/EffortPanel.test.tsx
  • 新增 src/components/EffortPanel/__tests__/effortPanelState.test.ts
  • 改造 src/commands/effort/effort.tsx:无参时返回 <EffortPanel>,有参维持原状
  • 运行 bun run precheck,必须零错误通过
  • commit message: feat(effort): /effort 无参时打开横向 slider 选择面板

Commit 2波纹动画基础稳定后再做

  • 新增 src/components/EffortPanel/rippleAnimation.ts
  • 新增 src/components/EffortPanel/useRippleFrame.ts
  • 新增对应测试
  • EffortPanel.tsx 中叠加渲染(仅 cursor === 'ultracode' 时)
  • 运行 bun run precheck
  • commit message: feat(effort): ultracode 档位铺满波纹背景动画

两阶段切开的好处:动画是创意工作,可能在调参上反复;基础功能稳定后即使动画翻车也能直接 revert 第二个 commit不影响主功能。

12. 验收清单

  • /effort 无参打开面板,光标停在当前生效档
  • ←/→ 移动光标,到边界不再继续
  • Enter 在 5 档之一时写 settings + AppState + 输出与 /effort X 同款消息
  • Enter 在 ultracode 时输出引导文案,不写任何状态
  • Esc 时不写任何状态,输出 "Effort unchanged."
  • env override 时顶部警告 + 双标记
  • 模型不支持时面板禁用,仅 Esc 可退出
  • /effort low|auto|help|current 等原有路径行为不变
  • bun run precheck 零错误