Files
Panama/docs/superpowers/specs/2026-08-24-input-redesign.md

8.1 KiB

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.