Skip to content

feat: find any action by typing, with Ctrl+K - #86

Merged
JackCuthbert merged 7 commits into
mainfrom
feat/command-palette
Aug 21, 2026
Merged

feat: find any action by typing, with Ctrl+K#86
JackCuthbert merged 7 commits into
mainfrom
feat/command-palette

Conversation

@JackCuthbert

Copy link
Copy Markdown
Owner

Closes #26.

Adds a command palette (Ctrl+K) that finds any action by typing, plus the nav and floating-button work that grew out of putting it on screen.

The palette

Navigation and app actions only — no per-todo actions, and no todo search, since the Search view already owns that. Results are grouped (Create / Go to view / Go to list / App) and each entry shows its chord where it has one. Ctrl+N / Ctrl+P move the selection as well as the arrows.

Commands are now a registry of their own (commands/lib/commands.ts) rather than an extension of the shortcut table. Not everything deserves a keybinding, and the previous shape forced one on anything that wanted to be listed. Shortcuts now reference a CommandId, so the two stay in sync without the registry dictating the keymap. Sign-out is deliberately excluded — it's in the footer, and a fuzzy match away from "Search" is not where you want to land.

Floating buttons

+ New todo and the palette square, reachable without opening the nav — on touch always, on desktop whenever the sidebar is collapsed. They sit inside mainScrollInner so they stay centred on the list while the sidebar animates, and sticky at the foot of the scroll port so a short list keeps them at the bottom of the screen rather than floating mid-page.

The palette button uses a terminal prompt rather than LuCommand: that glyph is ⌘, and this app binds Ctrl on every platform and refuses metaKey outright. An icon naming the one modifier the app never uses would be wrong on a Mac and meaningless everywhere else.

Sidebar rebalance

Adding a fourth button at the top made the nav read as three unrelated zones — chrome, then ghost-text views, then boxed-in lists. So the rows were brought onto one shape:

  • List rows lose the segmented shell (border, shadow, inner divider) and take the same box as the views: one row shape, a soft wash for hover, and the same wash for selection.
  • Group headings for Views and Lists, matching quick add's, which does the separating the dividers used to.
  • New list moved to the foot of the lists it adds to, styled as a row in that group rather than a button parked underneath — italic, because it's an action about the group rather than a list in it.
  • The filter moved into the Lists heading. A heading carries at most one button: two sat there briefly and read as a small toolbar that had drifted into a label. If a group ever needs more, that control becomes a menu instead of growing a neighbour.
  • The kebab is now LuEllipsisVertical rather than the character. As text it drew 7×16 of thin ink at weight 400 beside a 14px sparkle stroked at 2 — no size reconciles those, because one is type and the other a mark. Both are react-icons/lu now, per CLAUDE.md's one-icon-set rule.
  • Long list names ellipsise. They never had: .link carried text-overflow: ellipsis but is a flex container, and that property only acts on a block's own inline content, so the declaration was inert and a 617px name ran on inside a 246px button.
  • Icon buttons align on their glyph centres, not their box edges. The filter is 24px against the rows' 34px, so matching right edges left the glyphs 5px apart down the column.

Tooltips are new (ui/tooltip), on the filter and the palette button. title never appears for a keyboard user, can't be styled, and can't show a chord in the app's own type.

Notes for review

  • Moving New list out of the heading changed its accessible name from + New list to New list, which broke 69 e2e tests — createList is used almost everywhere. The six call sites were updated rather than re-adding the label, since the accessible name should match the visible text.
  • README screenshots regenerated, since the nav changed what they show.
  • The list filter is deliberately not a command, so the filter button is the only route to it on touch. That's why both heading buttons stayed visible on mobile rather than being hidden as desktop-only. Worth deciding separately whether the filter should become a command.
  • bun run test:e2e 78 passed, bun run test 751 passed, lint / typecheck / knip / fmt clean. Each commit typechecks and passes knip standalone.

Still open

The user guide (apps/docs/guide) has no command palette page yet. Happy to add it here or as a follow-up.

Settles the questions issue #26 left open: what the palette contains, that
commands and keyboard shortcuts are separate things, and that searching
todos stays with the Search view.
Groundwork for the command palette (#26). A shortcut now names a command
rather than describing itself, so an action is called the same thing
wherever it appears.
One field that reaches everything: create a todo or a list, jump to any
view or any list by name, open Settings or Help. Type to filter, arrow or
Ctrl+N/P to choose, Enter to run.
The two things done most often were behind the nav — a drawer on a phone,
a collapsed sidebar on desktop. They now sit at the foot of the list
whenever the nav is not showing.
Commands sits beside New todo at the top of the nav. New list moves to
the foot of the lists it creates, which is where you look when you want
another one.
The lists lose their boxed-in look and now match the views above them:
one shared row shape, a soft wash for hover and selection, and group
headings for Views and Lists. New list sits at the foot of the lists it
adds to, and the filter moved up into the Lists heading. Long list names
now shorten with an ellipsis instead of running past the edge.

The filter and the command palette button name themselves on hover.
@JackCuthbert
JackCuthbert merged commit 5159412 into main Aug 21, 2026
3 checks passed
@JackCuthbert
JackCuthbert deleted the feat/command-palette branch August 21, 2026 01:17
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.

Universal command palette (Cmd/Ctrl+K)

1 participant