diff --git a/e2e/changepw-form.playwright.ts b/e2e/changepw-form.playwright.ts new file mode 100644 index 0000000..1a62de6 --- /dev/null +++ b/e2e/changepw-form.playwright.ts @@ -0,0 +1,56 @@ +// 增量 3 验收(2026-10-12 诊断报告):改密弹窗表单语义——
)} ) diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index cd426fb..51ca5d1 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -112,6 +112,16 @@ export function SettingsModal() { ) } +// 输入框语义(2026-10-12 增量 3):仅秘密字段用密码框;普通文本用 text—— +// 无条件 type="password" 会把整排设置项当凭据表单(浏览器「password 不在 form 中/ +// 缺少 username」警告来源之一),也把普通文本无谓遮蔽。 +export function settingInputAttrs(desc: SettingDesc): { type: 'text' | 'password'; autoComplete: string } { + const isSecret = desc.secret || desc.type === 'secret' + return isSecret + ? { type: 'password', autoComplete: 'new-password' } + : { type: 'text', autoComplete: 'off' } +} + export function SettingInput({ desc, value, onChange }: { desc: SettingDesc; value: any; onChange: (v: any) => void }) { if (desc.type === 'boolean') { return onChange(e.target.checked)} /> @@ -122,7 +132,8 @@ export function SettingInput({ desc, value, onChange }: { desc: SettingDesc; val } const isSecret = desc.secret || desc.type === 'secret' - return onChange(e.target.value)} /> } diff --git a/test/settings-input.test.ts b/test/settings-input.test.ts new file mode 100644 index 0000000..6298107 --- /dev/null +++ b/test/settings-input.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest' +import { settingInputAttrs } from '../src/components/SettingsModal' + +// 2026-10-12 增量 3:配置输入按字段类型渲染——此前 SettingInput 默认分支无条件 +// type="password",浏览器把整排设置项当凭据表单(「password 不在 form 中/缺少 +// username」警告的来源之一),且普通文本被无谓遮蔽。新契约:仅秘密字段用密码框。 +describe('settingInputAttrs', () => { + it('secret=true → 密码框 + new-password', () => { + expect(settingInputAttrs({ key: 'llm.apiKey', label: '密钥', type: 'string', secret: true } as any)) + .toEqual({ type: 'password', autoComplete: 'new-password' }) + }) + + it('type=secret → 密码框', () => { + expect(settingInputAttrs({ key: 'x', label: 'X', type: 'secret' } as any).type).toBe('password') + }) + + it('普通文本 → text + off(不遮蔽、不当凭据)', () => { + const a = settingInputAttrs({ key: 'llm.baseUrl', label: '地址', type: 'string' } as any) + expect(a).toEqual({ type: 'text', autoComplete: 'off' }) + }) + + it('无类型标注的默认项 → text', () => { + expect(settingInputAttrs({ key: 'ui.language', label: '语言' } as any).type).toBe('text') + }) +})