One command list in Settings, with a Palette column - #621
Merged
Conversation
Settings had two lists of the same commands: a searchable, category-grouped
keybinding editor, and a flat unsearchable column of visibility buttons. Merge
them into the keybinding editor and delete the other.
The keybinding list wins because it is built from the FULL command catalog, not
the picker-filtered subset — so the merged list is itself the "show all
commands" surface. A user who hid something finds it by searching Settings and
re-ticking the box. That is why this adds no reveal-all toggle and no bulk
enable/disable: the merge removes the need for both. SHOW_HIDDEN_COMMANDS stays
a programmatic escape hatch.
The column is labelled Palette, never Enabled. Unticking hides the command from
the picker LIST and nothing else; it stays executable by the chord shown on the
same row, by the native menu, and by programmatic dispatch. Treating picker
visibility as an on/off switch is how a cosmetic "tidy my palette" preference
once silently killed File -> New Tab.
Three row states, not two, because the populations differ and the group gate
adds a case:
- editable: the ordinary checkbox
- excluded: em-dash for commands the palette structurally never lists, where
the old list rendered a live switch that persisted an override and could
never change anything
- group-suppressed: disabled + explanation for members of a disabled command
group. This FIXES a live defect — the old list drew all six navigation
commands as ON while the palette omitted them, because the group gate
outranks per-command overrides, so ticking one wrote an override that
changed nothing. Settings stated the opposite of what the user could see.
The write rule (prune the override when it equals the declared default) moves
next to the read rule in pickerVisibility.ts, so both halves of the same policy
have one home. settingsRegistry.ts previously held the write rule inline AND a
drifted second copy of the read rule; both are gone, along with
listPickerCommandMeta and PickerCommandMeta, now dead.
Search covers the new concern: the haystack includes the declared tier and
"hidden", so typing either finds what you came for.
Resets stay separate. One control doing both would mean restoring shortcuts
silently un-hides every command the user deliberately tidied away.
Verified: tsc clean on both projects, 1711 tests / 251 files green,
check:keybindings OK.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both reviewers cleared the core: picker visibility stays presentation-only, no command lost or gained a row, and the prune rule is correct for all eight tier/value combinations. Seven fixes below. CORRECTION FIRST. The previous commit message and the plan claimed this PR fixed a live defect — that Settings showed all six navigation commands as ON while the palette hid them. That was WRONG and Codex caught it. At base 1e4f2d8, resolveCommandVisible already delegated to isVisibleInPicker INCLUDING commandGroup, so the old list computed the checked state correctly. That bug existed once and was fixed in an earlier PR; the past-tense comment on PickerCommandMeta.commandGroup describing it is what misled the claim. What the third row state actually adds is smaller: those rows used to render an enabled, unticked checkbox whose click wrote an override the group gate silently outranked. Now the row is disabled and names the parent switch. Better affordance, not a correctness fix. The plan and the code comment now say so. ACCESSIBILITY. The checkbox had no accessible name on any of 98 rows — the wrapping label holds no text, so screen readers announced "checkbox, unchecked" 98 times with no indication of which command. And the suppressed state explained itself only through `title`, which is mouse-hover-only on a control that is out of the tab order anyway. Both now carry aria-label. SEARCH. Deleting the old row deleted its Settings-search vocabulary with it, so typing "hide", "hidden", "visibility" or "visible" into Settings returned nothing at all even though the control was right there. The surviving row now carries both vocabularies, its description covers both concerns, and the Commands category description no longer claims the category is only about the picker. DUPLICATED RULE. paletteState re-implemented the group gate as a literal `commandGroup === 'navigation'` test — a second copy of the rule, in the PR that consolidated the other half, and one that does not generalize: teaching isVisibleInPicker about a second gated group would leave Settings rendering an enabled checkbox that snaps back when ticked. Extracted suppressingCommandGroup() as the single implementation, and made COMMAND_GROUP_LABELS Record<CommandGroup, string> so a new group is a compile error rather than a bare id shown to the user. Also: the in-list "hidden" search token now covers group-suppressed rows (which ship suppressed by default, so they were the likeliest reason to search for it and the only ones excluded); the column header moved inside the scroll container and sticks, since outside it drifted by the scrollbar width; and the orphaned JSX comment left behind by the deleted branch is gone. TESTS. setPickerVisibilityOverride shipped with none — the prune rule is the half most likely to be "simplified" into `next[id] = visible`, and the suite would have stayed green. Ten new cases cover both directions for every tier, a round-trip property against isVisibleInPicker, non-mutation, the undefined map, and suppressingCommandGroup's agreement with the gate. Verified: tsc clean on both projects, 1721 tests / 251 files green, check:keybindings OK. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Settings had two lists of the same commands. This merges them into one.
Palettecheckbox on the rightWhy the keybinding list wins
It is built from the full command catalog, not the picker-filtered subset. So the merged list is itself the "show all commands" surface — a user who hid something finds it by searching Settings and re-ticking the box.
That is why this PR adds no reveal-all toggle and no bulk enable/disable. The merge removes the need for both.
SHOW_HIDDEN_COMMANDSstays what it is: a programmatic escape hatch.The column is
Palette, neverEnabledUnticking hides the command from the picker list and nothing else. It stays executable by the chord shown on the same row, by the native menu, and by programmatic dispatch.
This naming is load-bearing.
pickerVisibility.tscarries a READ-THIS block because treating picker visibility as an on/off switch already caused a real regression once: the File menu resolved ids against the picker-filtered registry, socommandVisibilityOverrides['new-tab'] = false— a cosmetic "tidy my palette" preference — silently killed File → New Tab.Three row states, not two
—for commands the palette structurally never lists. The old list rendered a live switch here that persisted an override and could never change anything, because the registry filters the command out before visibility logic runs.Cleanup
The write rule (prune the override when it equals the declared default) moves next to the read rule in
pickerVisibility.ts, so both halves of one policy have one home.settingsRegistry.tspreviously held the write rule inline and a second copy of the read rule. Both gone, along withlistPickerCommandMetaandPickerCommandMeta, now dead.Review
Two orchestrated reviewers — one Codex, one Claude. Both cleared the core: visibility never reaches an execution path, no command lost or gained a row, and the prune rule is correct for all eight tier/value combinations. Seven fixes applied:
titleon a control that is out of the tab order.hide/visibilityinto Settings returned nothing. Both vocabularies now live on the surviving row.paletteStatere-implemented the group gate as a literal'navigation'test, which would not generalize to a second gated group. ExtractedsuppressingCommandGroup()as the single implementation;COMMAND_GROUP_LABELSis nowRecord<CommandGroup, string>so a new group is a compile error.setPickerVisibilityOverrideshipped with none. Ten new cases cover both directions for every tier, a round-trip property againstisVisibleInPicker, non-mutation, and the gate agreement.hiddensearch token now covers group-suppressed rows, which ship suppressed by default and were the likeliest reason to search for it.Verification
tscclean on both projects (raw — electron-vite and vitest don't type-check), 1721 tests / 251 files green,check:keybindingsOK.Plan:
docs/superpowers/plans/2026-07-28-unified-command-settings.md🤖 Generated with Claude Code