Skip to content

One command list in Settings, with a Palette column - #621

Merged
Juliusolsson05 merged 2 commits into
mainfrom
feat/unified-command-settings
Jul 28, 2026
Merged

One command list in Settings, with a Palette column#621
Juliusolsson05 merged 2 commits into
mainfrom
feat/unified-command-settings

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Settings had two lists of the same commands. This merges them into one.

Before After
Keyboard Shortcuts — searchable, grouped by category, full command catalog Commands and Shortcuts — same list, plus a Palette checkbox on the right
Command Picker Visibility — flat, unsearchable column of buttons (deleted)

Why 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_COMMANDS stays what it is: a programmatic escape hatch.

The column is 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.

This naming is load-bearing. pickerVisibility.ts carries 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, so commandVisibilityOverrides['new-tab'] = false — a cosmetic "tidy my palette" preference — silently killed File → New Tab.

Three row states, not two

  • editable — ordinary checkbox
  • excluded 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.
  • group-suppressed — disabled + names the parent switch, for members of a disabled command group.

Correction. An earlier version of this description claimed the third state fixed a live defect — that Settings showed all six navigation commands as ON while the palette hid them. That was wrong, and the Codex reviewer caught it. At base 1e4f2d8f, resolveCommandVisible already delegated to isVisibleInPicker including commandGroup; that bug existed once and was fixed in an earlier PR. What this actually adds is smaller: those rows used to render an enabled, unticked checkbox whose click wrote an override the group gate silently outranked. Better affordance, not a correctness fix.

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.ts previously held the write rule inline and a second copy of the read rule. Both gone, along with listPickerCommandMeta and PickerCommandMeta, 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:

  1. Accessible name — the checkbox had none on any of 98 rows, and the suppressed state explained itself only via title on a control that is out of the tab order.
  2. Settings search — deleting the old row deleted its search vocabulary, so typing hide / visibility into Settings returned nothing. Both vocabularies now live on the surviving row.
  3. Duplicated rulepaletteState re-implemented the group gate as a literal 'navigation' test, which would not generalize to a second gated group. Extracted suppressingCommandGroup() as the single implementation; COMMAND_GROUP_LABELS is now Record<CommandGroup, string> so a new group is a compile error.
  4. The false claim above.
  5. TestssetPickerVisibilityOverride shipped with none. Ten new cases cover both directions for every tier, a round-trip property against isVisibleInPicker, non-mutation, and the gate agreement.
  6. hidden search token now covers group-suppressed rows, which ship suppressed by default and were the likeliest reason to search for it.
  7. Sticky header inside the scroll container (outside, it drifted by the scrollbar width), and the orphaned JSX comment removed.

Verification

tsc clean on both projects (raw — electron-vite and vitest don't type-check), 1721 tests / 251 files green, check:keybindings OK.

Plan: docs/superpowers/plans/2026-07-28-unified-command-settings.md

🤖 Generated with Claude Code

Juliusolsson05 and others added 2 commits July 28, 2026 16:22
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>
@Juliusolsson05
Juliusolsson05 merged commit f767a62 into main Jul 28, 2026
1 check passed
@Juliusolsson05
Juliusolsson05 deleted the feat/unified-command-settings branch July 28, 2026 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant