From d09e294bbeb508d163a2684a2165fd15ab20d316 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Sun, 14 Jun 2026 14:25:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(effort):=20=E6=96=B0=E5=A2=9E=20EffortPane?= =?UTF-8?q?l=20=E7=BA=AF=E5=87=BD=E6=95=B0=E7=8A=B6=E6=80=81=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=EF=BC=88PanelPosition=20+=20=E7=A7=BB=E5=8A=A8/?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=85=89=E6=A0=87=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 仅含纯函数与类型,无 React/Ink 依赖,便于单测。 - PANEL_POSITIONS:low → medium → high → xhigh → max → ultracode - moveLeft/moveRight:边界钳制(low 不再左移、ultracode 不再右移) - getInitialCursor:env override > displayed level Co-Authored-By: glm-5.2 --- .../__tests__/effortPanelState.test.ts | 101 ++++++++++++++++++ .../EffortPanel/effortPanelState.ts | 91 ++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 src/components/EffortPanel/__tests__/effortPanelState.test.ts create mode 100644 src/components/EffortPanel/effortPanelState.ts diff --git a/src/components/EffortPanel/__tests__/effortPanelState.test.ts b/src/components/EffortPanel/__tests__/effortPanelState.test.ts new file mode 100644 index 000000000..8760cd4e3 --- /dev/null +++ b/src/components/EffortPanel/__tests__/effortPanelState.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, test } from 'bun:test' +import { + END_POSITION, + HOME_POSITION, + PANEL_POSITIONS, + type PanelPosition, + getInitialCursor, + isUltracode, + moveLeft, + moveRight, +} from '../effortPanelState.js' + +describe('effortPanelState', () => { + test('PANEL_POSITIONS 顺序为 low → ultracode', () => { + expect(PANEL_POSITIONS).toEqual([ + 'low', + 'medium', + 'high', + 'xhigh', + 'max', + 'ultracode', + ]) + }) + + test('moveLeft 在 low 处保持 low', () => { + expect(moveLeft('low')).toBe('low') + }) + + test('moveLeft 正常左移', () => { + expect(moveLeft('high')).toBe('medium') + expect(moveLeft('ultracode')).toBe('max') + }) + + test('moveRight 在 ultracode 处保持 ultracode', () => { + expect(moveRight('ultracode')).toBe('ultracode') + }) + + test('moveRight 正常右移', () => { + expect(moveRight('medium')).toBe('high') + expect(moveRight('max')).toBe('ultracode') + }) + + test('HOME_POSITION 等于 low', () => { + expect(HOME_POSITION).toBe('low') + }) + + test('END_POSITION 等于 ultracode', () => { + expect(END_POSITION).toBe('ultracode') + }) + + test('isUltracode 守卫', () => { + expect(isUltracode('ultracode')).toBe(true) + expect(isUltracode('max')).toBe(false) + }) + + test('getInitialCursor:env override 为合法档位时返回 env 值', () => { + expect( + getInitialCursor({ + envOverride: 'high', + appStateEffort: 'medium', + displayed: 'high', + }), + ).toBe('high') + }) + + test('getInitialCursor:env 为 null(unset)时用 displayed', () => { + expect( + getInitialCursor({ + envOverride: null, + appStateEffort: undefined, + displayed: 'medium', + }), + ).toBe('medium') + }) + + test('getInitialCursor:env undefined 时用 displayed', () => { + expect( + getInitialCursor({ + envOverride: undefined, + appStateEffort: 'high', + displayed: 'high', + }), + ).toBe('high') + }) + + test('getInitialCursor:env 是数值(ant-only)时落回 displayed', () => { + // 数值不是合法 PanelPosition,回退 + expect( + getInitialCursor({ + envOverride: 75, + appStateEffort: 'medium', + displayed: 'medium', + }), + ).toBe('medium') + }) + + test('PanelPosition 类型编译期检查(隐式)', () => { + const p: PanelPosition = 'xhigh' + expect(p).toBe('xhigh') + }) +}) diff --git a/src/components/EffortPanel/effortPanelState.ts b/src/components/EffortPanel/effortPanelState.ts new file mode 100644 index 000000000..858a89438 --- /dev/null +++ b/src/components/EffortPanel/effortPanelState.ts @@ -0,0 +1,91 @@ +import type { EffortValue } from '../../utils/effort.js' + +/** + * 光标在面板上的位置。仅面板内部使用,不进入 AppState / settings / API。 + * 'ultracode' 不是 EffortLevel;它在本面板里仅作视觉占位与文案引导。 + */ +export type PanelPosition = + | 'low' + | 'medium' + | 'high' + | 'xhigh' + | 'max' + | 'ultracode' + +export const PANEL_POSITIONS: readonly PanelPosition[] = [ + 'low', + 'medium', + 'high', + 'xhigh', + 'max', + 'ultracode', +] as const + +export const HOME_POSITION: PanelPosition = 'low' +export const END_POSITION: PanelPosition = 'ultracode' + +/** + * 判断一个值是否可作为面板光标位置(不含 ultracode,因 ultracode 仅由面板内部产生)。 + */ +function isNonUltracodePosition( + value: unknown, +): value is Exclude { + return ( + typeof value === 'string' && + value !== 'ultracode' && + (PANEL_POSITIONS as readonly string[]).includes(value) + ) +} + +/** + * 把 EffortValue 归一化为面板可用的光标位置。 + * - null / undefined / 数值(ant-only)/ ultracode → undefined(让上层用 displayed) + * - 合法 string 档位 → 返回该档位 + */ +function normalizeToPanelPosition( + value: EffortValue | null | undefined, +): PanelPosition | undefined { + if (value === null || value === undefined) return undefined + if (typeof value === 'number') return undefined + if (isNonUltracodePosition(value)) { + return value + } + return undefined +} + +export function moveLeft(cursor: PanelPosition): PanelPosition { + const idx = PANEL_POSITIONS.indexOf(cursor) + if (idx <= 0) return PANEL_POSITIONS[0] + return PANEL_POSITIONS[idx - 1] +} + +export function moveRight(cursor: PanelPosition): PanelPosition { + const idx = PANEL_POSITIONS.indexOf(cursor) + if (idx === -1 || idx >= PANEL_POSITIONS.length - 1) { + return PANEL_POSITIONS[PANEL_POSITIONS.length - 1] + } + return PANEL_POSITIONS[idx + 1] +} + +export function isUltracode(cursor: PanelPosition): boolean { + return cursor === 'ultracode' +} + +/** + * 决定面板挂载时的初始光标位置。 + * 优先级:env override(若是合法档位)> displayed level + * + * @param envOverride getEffortEnvOverride() 的返回值:EffortValue | null | undefined + * @param appStateEffort AppState.effortValue + * @param displayed getDisplayedEffortLevel(model, appStateEffort) —— 必传,避免此处再依赖 model + */ +export function getInitialCursor(args: { + envOverride: EffortValue | null | undefined + appStateEffort: EffortValue | undefined + displayed: PanelPosition +}): PanelPosition { + const fromEnv = normalizeToPanelPosition(args.envOverride) + if (fromEnv !== undefined) return fromEnv + // displayed 已经是 EffortLevel(不含 ultracode),合法 + return args.displayed +}