## sensitive_input (`poetry_sensitive_input`)

A masked secret field with a reveal toggle and a copy button.

Class: Poetry::Ui::SensitiveInput::Component - BEM block `poetry-ui-sensitive_input`.
- `copy:` (boolean) - default false - Adds the copy-without-revealing button in the trailing cell.
- `described_by:` (string) - aria-describedby on the input - Field hint/error wiring.
- `disabled:` (boolean) - default false - Disables the input and drops the masked group's tab stop.
- `id:` (string) - The input's DOM id - what a Field label's for: must reference.
- `invalid:` (boolean) - default false - aria-invalid on the input - set by Field/FormBuilder from model errors.
- `label:` (string) - The accessible name - feeds the input's aria-label and the masked announcement ("{label}, masked."); pair with a visible Label/Field caption.
- `name:` (string) - required - The form field name on the real input.
- `placeholder:` (string) - Hint text shown while the field is empty.
- `readonly:` (boolean) - default false - The value can be revealed and copied but not edited.
- `required:` (boolean) - default false - Marks the real input required.
- `value:` (string) - The secret's current value; present = first paint is masked, blank = the empty state.
- PART `sensitive-input` - Root - the state machine rides here | states: data-state=masked|revealed|empty (always - masked (value hidden, group is the reveal button), revealed, or empty); data-copied (copy: only - stamped for a beat after a successful copy (the clipboard-text engine)); data-disabled (disabled: - the masked group loses its tab stop and pointer affordances)
- PART `sensitive-input-group` - The bordered field surface (InputGroup's chrome) - clicks anywhere on it reveal; wears the focus ring when the mask button inside holds focus
- PART `input-group-control` - The real input - rendered in every state for layout stability; inert while masked (aria-hidden, tabindex -1, readonly, transparent); type=password unless revealed
- PART `sensitive-input-mask` - The overlay painting the masked state - while masked it IS the reveal button (role=button, tabindex 0, "{label}, masked.", described by the sr hint; only text spans inside, so no nested-interactive); bullet dots swap to the reveal hint on hover/focus with no layout shift
- PART `input-group-addon` - The trailing cell holding the eye (and copy: affordance) | states: data-align=inline-end (always - inline-end holds the actions)
- WIRING root: `poetry--core--sensitive-input` registers; values masked_label, hidden_message, read_only (if readonly); actions blurred on focusout | `poetry--core--clipboard-text` (if copy) registers; values message
- WIRING group: `poetry--core--sensitive-input` actions reveal on click
- WIRING mask: `poetry--core--sensitive-input` actions maskKeydown on keydown; targets mask
- WIRING input: `poetry--core--sensitive-input` actions changed on input, inputKeydown on keydown; targets input | `poetry--core--clipboard-text` (if copy) targets input
- WIRING toggle: `poetry--core--sensitive-input` actions toggle on click; targets toggle
- WIRING copy_button: `poetry--core--clipboard-text` actions copy on click
- WIRING hint: `poetry--core--sensitive-input` targets hint
- RULE: Secrets shown-on-demand are a SensitiveInput (poetry_sensitive_input) - never a bare password Input with a hand-rolled eye; the masked-container contract (role=button, focus discipline, blur re-mask) rides the controller.
- RULE: label: feeds the masked announcement ("{label}, masked.") - pair with a Label/Field for the visible caption.
- RULE: copy: true adds copy-without-revealing; leave it off for password-change forms.
- RULE: Values re-mask on blur BY DESIGN - do not fight it with reveal-state persistence.
