## input_otp (`poetry_input_otp`)

A fixed-length, segmented input for one-time passcodes.

Class: Poetry::Ui::InputOtp::Component - BEM block `poetry-ui-input_otp`.
- `disabled:` (boolean) - default false - Disables the native input (the whole row dims).
- `groups:` () - Cell clustering, e.g. [3, 3] -> two groups with a separator.
- `invalid:` (boolean) - default false - aria-invalid on the input; the cells mirror the destructive treatment (set by Field/FormBuilder from the failed verify).
- `length:` (integer) - required - Code length = slot count = maxlength.
- `name:` (string) - required - The ONE input serializes params[name] = the code string.
- `pattern:` () - default "digits" - :digits (numeric keypad) | :alphanumeric | a custom Regexp - the per-char filter + the native pattern attribute + inputmode.
- `required:` (boolean) - default false - aria-required on the input - never native required (the Field rule: required rides server-side validation + aria).
- `separator:` (boolean) - default true - role=separator dash between groups (meaningful with 2+ groups).
- `value:` (string) - Current code (server-rendered into the input AND the cells). The FormBuilder deliberately never round-trips it (a rejected code is dead).
- PART `input-otp-container` - Root row (forced dir=ltr - slot order equals string index order even on RTL pages) wrapping the real input and the mirror cells
- PART `input-otp` - THE real native <input> (autocomplete one-time-code) stretched invisibly over the row - the only AT and serialization surface
- PART `input-otp-group` - One aria-hidden cluster of mirror cells (groups: clustering)
- PART `input-otp-slot` - One presentational mirror cell - paints its char and the active-cell ring | states: data-active=true|false ("true" while the native caret sits on this cell (the controller projects selectionStart while the input is focused; the server renders "false"))
- PART `input-otp-caret` - The fake-caret overlay - hidden server-side; the controller unhides it on the active EMPTY cell
- PART `input-otp-separator` - The between-groups dash - role=separator kept for parity but aria-hidden (a recorded divergence)
- WIRING root: `poetry--core--otp` registers; values length, pattern; actions focusInput on click
- WIRING input: `poetry--core--otp` actions sync on input/focus/blur, paste on paste; targets input
- WIRING slot: `poetry--core--otp` targets slot
- RULE: Use poetry_input_otp / form.otp_field - NEVER build per-cell inputs (n Tab stops, broken paste, broken SMS autofill, unnameable cells).
- RULE: Label via Field always ('Verification code'); put the length in the hint.
- RULE: groups must sum to length (ArgumentError).
- RULE: Do NOT auto-submit on poetry:otp:complete without a visible confirm affordance - silent submit on the 6th keystroke strands users who mistyped char 3.
- RULE: Never pre-fill value: with a real code in previews/test fixtures beyond dummies; never log the value (it is a live credential).
- RULE: InputOTP is for CODES - passwords use Input type=password, longer identifiers use Input.
