| 1 | # Hotbar QA Matrix |
| 2 | |
| 3 | This matrix is the v0.8.66 release gate for #3401. It ties the shipped Hotbar |
| 4 | MVP to repeatable checks instead of treating isolated unit tests as sufficient |
| 5 | coverage. |
| 6 | |
| 7 | ## Support Level |
| 8 | |
| 9 | | Source | Support level | Release statement | |
| 10 | | --- | --- | --- | |
| 11 | | Built-in app actions | Supported | Default slots dispatch existing app paths through `AppAction` or in-app state mutation. | |
| 12 | | Slash commands | Supported | Argument-free and optional-argument commands dispatch through `commands::execute`; required-argument commands prefill the composer. | |
| 13 | | MCP tools/resources/prompts | Deferred | Visible through the command palette/MCP manager only until argument and approval gates are wired. | |
| 14 | | Skills | Deferred | Visible through command palette and slash skill activation; direct Hotbar binding is deferred until activation receipts are wired. | |
| 15 | | Plugins | Deferred | Visible through `/plugins`; direct Hotbar binding is deferred until plugin approval gates are wired. | |
| 16 | |
| 17 | ## Config States |
| 18 | |
| 19 | | Scenario | Expected behavior | Evidence | |
| 20 | | --- | --- | --- | |
| 21 | | No hotbar config | Default slots resolve to the shipped eight-slot bar. | `crates/config/src/tests.rs::hotbar_defaults_when_config_is_absent` | |
| 22 | | Empty hotbar config | `hotbar = []` disables all default slots. | `crates/config/src/tests.rs::hotbar_empty_array_disables_default_slots`; `crates/tui/src/config_persistence.rs::persist_hotbar_bindings_writes_empty_array_to_disable_defaults` | |
| 23 | | Partial config | Missing slots render empty without filling from defaults. | `crates/tui/src/tui/sidebar.rs::hotbar_panel_slots_handle_empty_partial_and_unknown_config` | |
| 24 | | Unknown actions | Unknown configured actions stay visible as unknown instead of being dropped silently. | `crates/config/src/tests.rs::hotbar_validation_warns_without_dropping_unknown_actions`; `crates/tui/src/tui/sidebar.rs::hotbar_panel_slots_handle_empty_partial_and_unknown_config` | |
| 25 | | Custom labels | Configured labels render and persist with bindings. | `crates/tui/src/tui/hotbar/actions.rs::recommended_hotbar_bindings_serialize_action_ids_and_labels`; `crates/tui/src/tui/ui/tests.rs::hotbar_setup_save_persists_bindings_to_config_path` | |
| 26 | | Workspace overlay | Project config does not replace user-owned Hotbar bindings. | `crates/config/src/tests.rs::project_merge_does_not_replace_user_hotbar_bindings` | |
| 27 | | Legacy/user config path | Fresh setup writes the primary config path; existing comments survive replacement. | `crates/tui/src/config_persistence.rs::persist_hotbar_bindings_writes_primary_config_path_for_fresh_installs`; `crates/tui/src/config_persistence.rs::persist_hotbar_bindings_preserves_comments_and_replaces_existing_tables` | |
| 28 | | Failed persistence | Live config and config file remain unchanged and an error is surfaced. | `crates/tui/src/tui/ui/tests.rs::hotbar_setup_save_error_leaves_live_config_and_file_unchanged` | |
| 29 | |
| 30 | ## UI States |
| 31 | |
| 32 | | Scenario | Expected behavior | Evidence | |
| 33 | | --- | --- | --- | |
| 34 | | Normal TUI/composer | `Alt-1` through `Alt-8` dispatch configured slots; bare digits remain text input. | `crates/tui/src/tui/ui/tests.rs::hotbar_alt_digit_fires_from_composer_and_sidebar_states`; `crates/tui/src/tui/ui/tests.rs::hotbar_bare_digit_inserts_text_even_when_composer_empty` | |
| 35 | | Hidden/sidebar focus states | Hotbar dispatch is still available from hidden, auto, pinned, and focused sidebar states. | `crates/tui/src/tui/ui/tests.rs::hotbar_alt_digit_fires_from_composer_and_sidebar_states` | |
| 36 | | Narrow sidebar | Hotbar panel keeps fixed two-row layout and bounded hover/status text. | `crates/tui/src/tui/sidebar.rs::hotbar_panel_lines_keep_two_fixed_rows_and_hover_status`; `docs/evidence/terminal-visual-regression-matrix.md` | |
| 37 | | Modal/overlay open | Modal, approval, picker, decision-card, and onboarding states block Hotbar numeric ownership. | `crates/tui/src/tui/ui/tests.rs::hotbar_digits_are_blocked_while_modal_or_onboarding_is_active`; `crates/tui/src/tui/ui/tests.rs::hotbar_alt_digit_is_blocked_while_inline_selectors_are_open`; `crates/tui/src/tui/ui/tests.rs::hotbar_alt_digit_is_blocked_while_decision_card_is_active` | |
| 38 | | Setup wizard open/save | Setup lists supported source categories, updates draft bindings, saves, and persists. | `crates/tui/src/tui/hotbar/setup.rs::wizard_sources_follow_registered_action_categories`; `crates/tui/src/tui/hotbar/setup.rs::wizard_save_emits_bindings_but_escape_only_closes`; `crates/tui/src/tui/ui/tests.rs::hotbar_setup_save_persists_bindings_to_config_path` | |
| 39 | | Restart/re-dispatch | Persisted bindings parse back into config and resolve through the same dispatch path. | `crates/config/src/tests.rs::hotbar_tables_parse_and_round_trip`; `crates/tui/src/tui/ui/tests.rs::hotbar_dispatches_bound_slot_and_ignores_empty_slot` | |
| 40 | |
| 41 | ## Dispatch Outcomes |
| 42 | |
| 43 | | Outcome | Expected behavior | Evidence | |
| 44 | | --- | --- | --- | |
| 45 | | Handled in-app | Local UI/state actions mutate app state and mark redraw when needed. | `crates/tui/src/tui/hotbar/actions.rs::sidebar_toggle_reports_visibility_and_dispatches`; `crates/tui/src/tui/hotbar/actions.rs::trust_toggle_reports_trust_state_and_dispatches` | |
| 46 | | `AppAction` return | Actions that must be handled by the event loop return the existing `AppAction`. | `crates/tui/src/tui/hotbar/actions.rs::compact_action_emits_existing_app_action`; `crates/tui/src/tui/ui/tests.rs::hotbar_dispatches_bound_slot_and_ignores_empty_slot` | |
| 47 | | Composer prefill | Required-argument slash commands prefill the composer instead of firing empty args. | `crates/tui/src/tui/hotbar/actions.rs::slash_hotbar_action_prefills_required_argument_command` | |
| 48 | | Disabled reason | Disabled actions are excluded from recommendations and report a reason if manually bound. | `crates/tui/src/tui/hotbar/actions.rs::hotbar_recommendations_exclude_disabled_actions`; `crates/tui/src/tui/ui/tests.rs::hotbar_bound_disabled_action_reports_reason_without_dispatching` | |
| 49 | | Unknown action | Unknown configured action is visible and does not dispatch. | `crates/tui/src/tui/sidebar.rs::hotbar_panel_slots_handle_empty_partial_and_unknown_config`; `crates/tui/src/tui/ui.rs::dispatch_hotbar_slot` | |
| 50 | | Approval-gated/deferred source | Source is explicitly deferred and must not register bindable actions before gates exist. | `crates/tui/src/tui/hotbar/actions.rs::source_descriptors_cover_dispatch_boundaries`; `crates/tui/src/tui/hotbar/actions.rs::deferred_sources_cannot_register_dispatchable_actions` | |
| 51 | |
| 52 | ## Terminal chord evidence for v0.9.2 (#3758) |
| 53 | |
| 54 | Codewhale receives terminal key events; it cannot force a terminal to forward the |
| 55 | macOS Option key as Meta. The source-level contract below is release-gated on |
| 56 | every platform. Terminal/device observations stay separate so an unrun terminal |
| 57 | is never presented as green. |
| 58 | |
| 59 | ### Automated event matrix |
| 60 | |
| 61 | | Input/state | Required result | Regression evidence | |
| 62 | | --- | --- | --- | |
| 63 | | `Alt-1` through `Alt-8` | Dispatch the corresponding slot | `hotbar_alt_digit_fires_from_composer_and_sidebar_states` | |
| 64 | | Bare `1` through `8` | Insert/retain ordinary composer input | `hotbar_bare_digit_inserts_text_even_when_composer_empty` | |
| 65 | | `Ctrl-number`, `Super/Cmd-number`, `Alt-0`, `Alt-9`, and F-keys | Never dispatch a Hotbar slot; `F1` remains help | `hotbar_slot_from_key_accepts_only_alt_one_through_eight` and global keybinding tests | |
| 66 | | AltGr-style `Ctrl+Alt+number` | Never dispatch, preserving non-US input ownership | `hotbar_slot_from_key_accepts_only_alt_one_through_eight` | |
| 67 | | Modal, onboarding, slash/history selector, approval, picker, or decision card | Owning surface blocks Hotbar dispatch | `hotbar_digits_are_blocked_while_modal_or_onboarding_is_active`, `hotbar_alt_digit_is_blocked_while_inline_selectors_are_open`, `hotbar_alt_digit_is_blocked_while_decision_card_is_active` | |
| 68 | | Default-hidden/disabled Hotbar | No visible accelerator surface and no slot dispatch | config/sidebar hidden-state tests listed above | |
| 69 | |
| 70 | ### Manual terminal matrix |
| 71 | |
| 72 | Record the exact app/version and the terminal key setting when a device pass is |
| 73 | run. A blank or `UNRUN` row is not release evidence. |
| 74 | |
| 75 | | OS | Terminal | Meta/Option setting | Alt/Option-1..8 | Bare/Cmd/F1 | Modal/default-hidden | Notes | |
| 76 | | --- | --- | --- | --- | --- | --- | --- | |
| 77 | | macOS | Terminal.app | UNRUN | UNRUN | UNRUN | UNRUN | Some Option settings emit characters rather than Meta. | |
| 78 | | macOS | iTerm2 | UNRUN | UNRUN | UNRUN | UNRUN | Record the Left/Right Option key mapping. | |
| 79 | | macOS | Ghostty | UNRUN | UNRUN | UNRUN | UNRUN | Record `macos-option-as-alt` or equivalent. | |
| 80 | | macOS | Kitty | UNRUN | UNRUN | UNRUN | UNRUN | Record any `macos_option_as_alt` setting. | |
| 81 | | Linux | terminal + layout | UNRUN | UNRUN | UNRUN | UNRUN | Include an AltGr/non-US layout pass. | |
| 82 | | Windows | Windows Terminal | UNRUN | UNRUN | UNRUN | UNRUN | Include the host shell and keyboard layout. | |
| 83 | |
| 84 | A terminal-specific caveat belongs in `docs/KEYBINDINGS.md` and `/hotbar help` |
| 85 | only after it is reproduced. The product must not advertise Cmd-number or |
| 86 | function-key aliases merely because a terminal can remap them. |
| 87 | |
| 88 | ## Release Smoke Checklist |
| 89 | |
| 90 | Run before claiming Hotbar MVP readiness: |
| 91 | |
| 92 | 1. `cargo test -p codewhale-config hotbar -- --nocapture` |
| 93 | 2. `cargo test -p codewhale-tui --bin codewhale-tui --locked hotbar::actions -- --nocapture` |
| 94 | 3. `cargo test -p codewhale-tui --bin codewhale-tui --locked hotbar_setup -- --nocapture` |
| 95 | 4. `cargo test -p codewhale-tui --bin codewhale-tui --locked hotbar_panel -- --nocapture` |
| 96 | 5. `cargo test -p codewhale-tui --bin codewhale-tui --locked hotbar_alt_digit -- --nocapture` |
| 97 | 6. `cargo test -p codewhale-tui --bin codewhale-tui --locked hotbar_dispatch -- --nocapture` |
| 98 | |
| 99 | Manual pass, if a release candidate binary is available: |
| 100 | |
| 101 | 1. Start with no `[hotbar]` config and verify the default eight slots render in |
| 102 | the sidebar with visible `Alt1` through `Alt8` accelerator labels. |
| 103 | 2. Open `/hotbar`, bind a slash command, save, restart, and verify the binding |
| 104 | persists. |
| 105 | 3. Press `Alt-1` through `Alt-8` from composer/sidebar states and verify only |
| 106 | `Alt` chords dispatch. |
| 107 | 4. Open command palette, slash menu, setup wizard, decision card, and an |
| 108 | approval modal; verify Hotbar digits are blocked while those surfaces own |
| 109 | input. |
| 110 | 5. Confirm MCP, skill, and plugin entries remain discoverable through their |
| 111 | existing command-palette or slash-command paths and are not offered as direct |
| 112 | Hotbar bindable actions. |
| 113 |