# Input redesign — keycaps, search, and honest state

Approved mock: `home-mocks/input.html` (scratchpad, :8642). Spec wins over mock on conflict.

## Goals

1. **Shortcuts browser**: the 130-row wall becomes one searchable card — keycap chips, group
   headers with counts, hover-revealed Change/Reset, inline capture. The rebinding engine
   (`Keybinds.rebind`, conflicts refused, `keybindOverrides`) already exists and is untouched.
2. **Typing card modernized**: dropdowns instead of ChoiceGrid tile walls; raw XKB string and
   variant into a collapsed Advanced section (still discoverable — a contract requires the raw
   string not be hidden *away*, collapsed-but-present satisfies it; C verifies the needle).
3. **Mouse & Touchpad**: dropdowns over wide segmented rows, four new option groups (below),
   a live try-it area, and a phantom-filtered device list.
4. **Dictation status-first**: ready hero, hotkeys as keycaps (looked up live from Keybinds so a
   rebind shows truthfully; literal fallback), guided setup steps with a real progress bar, and
   an on-page test field that uses the existing typing pipeline.

Non-goals: per-device settings (phantom-heavy device list, zero plumbing — deliberately
skipped), editing keybinds.lua actions (chords only, as today), touch/tablet options
(no hardware), accel `custom` curves.

## New schema keys (group / hypr option — agent A verifies each option's exact name, type, and
value map against `hyprctl descriptions` before writing the entry; enum-hypr-map-contract and
schema-hypr-shape-contract must hold; every key also gets its `prefs.get()` read-back in
`config/dot/hypr/input.lua` with matching defaults — hypr-prefs-contract):

| Key | Group | Hyprland option | UI |
|---|---|---|---|
| `focusOnClose` | pointer | `input:focus_on_close` | dropdown: "Most recently used" / "Under the pointer" |
| `cursorHideWhileTyping` | pointer | `cursor:hide_on_key_press` | toggle "Hide pointer while typing" |
| `cursorWarpOnWorkspaceChange` | pointer | `cursor:warp_on_change_workspace` | toggle "Jump pointer to the focused display" |
| `scrollMethod` | pointer | `input:scroll_method` | dropdown (offer only map-published values; include "On a held button" only if the map allows) |
| `scrollButton` | pointer | `input:scroll_button` | int row, visible only when scrollMethod is the button one |
| `touchpadClickfinger` | touchpad | `input:touchpad:clickfinger_behavior` | toggle "Two-finger right-click" |
| `touchpadTapAndDrag` | touchpad | `input:touchpad:tap-and-drag` | toggle "Tap and drag" |

Defaults = today's effective Hyprland/input.lua values so shipping changes nothing. If an
option's published map/type makes a row above impossible as specced (e.g. scroll_method values),
implement what the map allows and flag the difference loudly.

## Service work (A)

- `InputDevices.qml`: add `realMice` / `realKeyboards` — name-filtered lists (exclude
  substrings: `consumer-control`, `virtual`, `video-bus`, `power-button`, `webcam`,
  `audio`, `uinput`, plus dedupe transceiver siblings by prefix), each entry `{ name, pretty }`
  (pretty = title-cased, dashes to spaces). Keep the existing flat lists and `hasTouchpad`
  untouched (contract-pinned behavior).
- Expose `mainKeyboardLayout` (from the main keyboard's live layout string) for the devices card.

## UI (B)

**ShortcutsPage.qml** (title stays "Keyboard"):
1. *Typing* card: Layout dropdown (curated common layouts: English (US), English (UK), German,
   French, Spanish, Nordic…, mapping to `keyboardLayout` codes; a stored code outside the list
   renders as the raw code and the dropdown offers "Custom…" which reveals Advanced), Caps Lock
   dropdown, Compose dropdown, Layout-switching dropdown (all four presets keep writing
   `keyboardOptions` through the existing page-local XKB helpers — xkb-presets-contract),
   combined Key repeat row (delay + rate sliders), Num Lock toggle, then **Advanced** expander:
   raw `keyboardOptions` mono field + `keyboardVariant`.
2. *Shortcuts* card: header "130 bound · N changed" (live counts), subtitle "Click Change and
   press the new keys. A shortcut another action holds is refused, never stolen.", filter field
   (matches description + group, case-insensitive), grouped rows in `Keybinds.groupOrder` with
   "showing N of M" when filtered; each row: description, CHANGED badge when overridden,
   hover-revealed Change/Reset (Reset only on overridden), keycap chord. Capture swaps the
   chord area for the existing `ShortcutCapture` inline. "Restore every shipped shortcut" row
   stays, with the live differs-count detail. Keep the no-GNOME-handoff comment.
3. New component **KeycapChord.qml**: parses a display chord ("SUPER + SHIFT + Q") into keycap
   chips — mono font, tabular figures, modifier caps tinted accent, "+" separators muted.
   Reused by DictationPage.

**MousePage.qml**: Mouse card (speed slow/fast, Acceleration dropdown, Scroll speed, Natural
scrolling, Left-handed, Middle-click paste, Scroll method dropdown + conditional Scroll button
row) · Touchpad card (existing rows + the two new toggles; visible on `hasTouchpad`) ·
Pointer behavior card (Focus dropdown — the 4 followMouse values, focusOnClose dropdown,
hide-while-typing, hide-after slider with "Never" zero, warp toggle, Pointer size) ·
**Try it** card (new `InputTestArea.qml`: a scribble Canvas — repaints only on pointer motion,
cleared by a corner button — and a scrollable text strip; purely local, no compositor writes) ·
**Connected devices** card from `InputDevices.realKeyboards/realMice` (+ touchpad when
present), with the subtitle noting phantoms are filtered. Gestures rows fold into the Touchpad
card (swipe distance + invert) — the separate Gestures card goes.

**DictationPage.qml**: ready hero (state tile ✓ / … / ✗, title, model+size line) · hotkey rows
with KeycapChord, chords looked up from `Keybinds.binds` by matching the dictate descriptions
(fallback literals if not found) · **Try it** row: a read-only-styled TextField + "Test
dictation" button that focuses the field and drives `panama-dictate start`/`stop` through the
existing Dictation service — dictated text lands in the field via the normal wtype pipeline, no
new script plumbing; detail explains it types here instead of your document · Microphone
ActionRow → Sound (unchanged) · setup state replaces the hero with numbered steps (container
image / speech model with progress bar from `downloadFraction` / first transcription), driven
by the existing `phase` fields; errors keep their row.

## Search & docs (C)

Extra entries: "Rebind a shortcut" → shortcuts; "Key repeat" auto via schema; "Pointer test
area" → mouse; "Scroll method" auto; "Connected input devices" → mouse. Existing entries stay.
Docs + launcher commands regenerate after schema lands (orchestrator).

## Contracts (C — write, never run)

- `xkb-presets-contract`: verify/adjust needles for the Advanced placement (raw string must
  remain present in the page source).
- `keybinds-contract` / `keybind-rebind-contract`: UI needles (Change/Reset/ShortcutCapture
  usage) reconciled with the rebuilt page; count-match and conflict rules unchanged.
- `enum-hypr-map-contract` / `schema-hypr-shape-contract` / `hypr-prefs-contract`: the seven
  new keys must satisfy all three (C statically replays where possible).
- `settings-pages-contract`, `gnome-handoff-contract` needles re-verified.
- `dictation-contract`: confirm the test-field flow doesn't violate the "where text lands" pins
  (it uses the normal pipeline; the page merely owns focus). Flag, don't force, if it conflicts.
- Backlog spec: Phase 8 section.

## Agent ownership (parallel)

- **A**: `config/PreferenceSchema.qml` (7 new keys), `config/dot/hypr/input.lua`,
  `services/InputDevices.qml`.
- **B**: `modules/settings/ShortcutsPage.qml`, `MousePage.qml`, `DictationPage.qml`, new
  components (`KeycapChord.qml`, `InputTestArea.qml`, others as needed) + `modules/settings/qmldir`.
- **C**: `services/SettingsSearch.qml`, the contracts above, backlog spec, README count line
  only if the count changes.

B programs against the schema keys and InputDevices API above; A must not change them without
updating this spec.
