优化(07-28-all-account-login-readiness): 调整密码框Tab顺序
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-08-23 19:10:34 +08:00
父节点 e7b516ed3e
当前提交 7b86ffb3c9
共修改 8 个文件,包含 149 行新增和 21 行删除
@@ -2712,3 +2712,70 @@ const MANAGEMENT_ACTIVE_PATHS = ["/people", "/system/users"];
isManagementPath(pathname, permissions);
<RequirePermission anyOf={getManagementRoutePermissions("people")}>...</RequirePermission>
```
## Scenario: Authentication Password Input Sequential Focus
### 1. Scope / Trigger
- Trigger: changing the shared password input or any login, password-change or password-recovery form.
- Purpose: preserve the domestic continuous-form Tab flow without letting the inline visibility control create an extra sequential stop.
### 2. Signatures
- Shared component: `client/src/components/ui/password-input.tsx`.
- Visibility control: native `button type="button"` with `tabIndex={-1}`, dynamic `aria-label`, boolean `aria-pressed` and `aria-controls=<input-id>`.
- Sequential focus follows DOM order; do not use positive `tabIndex` values.
### 3. Contracts
- Pressing Tab from a password input skips its inline visibility button and reaches the next sequential form control in the DOM.
- Existing links such as `忘记密码?` remain keyboard reachable; do not remove unrelated controls merely to make a password-to-submit shortcut.
- Mouse and touch can still toggle visibility. The label changes between `显示密码` and `隐藏密码`, pressed state follows visibility, and the input value is preserved.
- Login, password-change and password-recovery pages reuse the shared component and therefore keep one focus contract.
- The focus change does not alter password validation, autofill, form submission, authentication APIs, email delivery or server configuration.
### 4. Validation & Error Matrix
- Tab lands on the inline eye button -> sequential-focus regression.
- Tab skips the next real input, link or submit control -> DOM-order regression; do not repair with a positive `tabIndex`.
- Visibility click changes type but clears the value -> input-state regression.
- Toggle lacks its dynamic label, pressed state or input association -> accessibility regression.
- Enter from the confirmation password input does not submit the valid form -> form-keyboard regression.
### 5. Good/Base/Bad Cases
- Good: reset form moves `新密码 -> 确认新密码 -> 重置密码`, while either eye control remains clickable and preserves the password.
- Base: login password moves to the existing `忘记密码?` link because that link is the next DOM control, then continues to login submit.
- Bad: keep the eye button in sequential Tab despite the approved form contract, remove all button semantics, or assign positive indices to force an artificial order.
### 6. Tests Required
- Playwright asserts the login password reaches the forgot-password link, password-change fields traverse old/new/confirm/submit, and recovery fields traverse new/confirm/submit.
- Assert `tabindex=-1`, `aria-controls`, dynamic `aria-label`, `aria-pressed`, click-to-toggle and value preservation.
- Assert Enter from the valid confirmation-password field submits the form.
- Run `cd client && npm run typecheck && npm run build`, focused authentication E2E, Trellis/process/sensitive gates and `git diff --check`.
### 7. Wrong vs Correct
#### Wrong
```tsx
<button type="button" aria-label="显示密码" onClick={toggleVisible}>
<Eye />
</button>
```
#### Correct
```tsx
<button
type="button"
tabIndex={-1}
aria-controls={inputId}
aria-label={visible ? "隐藏密码" : "显示密码"}
aria-pressed={visible}
onClick={toggleVisible}
>
<Eye aria-hidden="true" />
</button>
```