Skip to content

feat(web): add extensible command palette#1103

Open
binbandit wants to merge 9 commits intopingdotgg:mainfrom
binbandit:t3code/extensible-command-palette
Open

feat(web): add extensible command palette#1103
binbandit wants to merge 9 commits intopingdotgg:mainfrom
binbandit:t3code/extensible-command-palette

Conversation

@binbandit
Copy link
Contributor

@binbandit binbandit commented Mar 15, 2026

Summary

  • add a layout-scoped command palette using the existing coss command primitives
  • wire commandPalette.toggle through contracts, server defaults, the sidebar trigger, and keybinding docs
  • share thread-creation logic between shortcuts and palette actions, and avoid command-palette subscriptions/work while the palette is closed

Validation

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/keybindings.test.ts in packages/contracts
  • bun run test -- src/keybindings.test.ts in apps/server
  • bun run test -- src/keybindings.test.ts in apps/web
  • bun run test:browser -- src/components/ChatView.browser.tsx -t "opens the command palette from the configurable shortcut and runs a command" in apps/web

Visual

Screenshot 2026-03-15 at 3 09 10 pm

Note

Add extensible command palette to the web UI with filesystem browsing

  • Adds a CommandPaletteProvider and OpenCommandPaletteDialog in CommandPalette.tsx that lets users search and execute actions: new thread, open/add projects, jump to recent threads, and open settings.
  • Binds mod+k to commandPalette.toggle (when not in terminal focus) via new keybinding infrastructure in both server and web client.
  • Adds filesystem browse mode (triggered by paths starting with /, ~/, ./) backed by a new filesystem.browse WebSocket RPC that returns up to 50 directory suggestions with Tab autocompletion.
  • Adds a 'Search commands' button in Sidebar.tsx displaying the shortcut label, and replaces inline relative time formatting with a new formatRelativeTime utility.
  • Behavioral Change: while the command palette is open, all other global keyboard shortcuts in ChatView and the chat route are suppressed.

Macroscope summarized 7bc39e6.

@coderabbitai
Copy link

coderabbitai bot commented Mar 15, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5f528c40-abf3-4d50-99e0-43f221f466f8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Mar 15, 2026
data-testid="command-palette"
>
<Command aria-label="Command palette" items={groups}>
<CommandInput placeholder="Search commands and threads..." />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is search still incoming? I don't see any code for that functionality and it doesn't appear to work on my end.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

Comment on lines +216 to +218
await navigate({
to: "/$threadId",
params: { threadId: thread.id },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue with your code, but something I noticed. If this is done to a thread that is hidden beneath "view more", that section doesn't expand, so it ends up looking like no sidebar item is highlighted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you point me to how this was addressed? Still seems to be happening to me on the latest pull.

@binbandit binbandit force-pushed the t3code/extensible-command-palette branch from 713d184 to 869181f Compare March 15, 2026 06:01
@Noojuno
Copy link
Contributor

Noojuno commented Mar 15, 2026

@binbandit Yours is obviously more of a strict command pallette than #1076, but it might be worth looking into.

I also think that some of the ordering of actions could be slightly different and be a slightly better workflow. I have been playing with my own command pallette in a branch, and I think for me the best structure wasn't a strict Actions/Threads split, but was more like this:

  • New
  • Recent threads
    • List of threads in order
  • Actions
    • Custom top bar actions for the current thread
    • Git actions
  • Settings
    • Just open settings page for now, but I think could list specific settings and take you straight to them in a future enhancement

I found that a lot of the "Actions" I initially had were actually pretty infrequently used, so I had to move down a lot to get to the stuff that I actually wanted. Especially stuff like "Settings", which should be very infrequently used, and can live at the very bottom of the list.

Not saying this is objectively better, but it's what I found worked for me after I originally had basically the order you have. Happy to push up my branch if you wanna see what I mean.

@binbandit
Copy link
Contributor Author

@Noojuno This is good feedback. Thanks.

I was going to get this in, then allow for users to drag and drop in settings to re-organise the palette etc. Choose what they want to see etc. -- Was going to do this after so as to not bloat this pr.

{ key: "mod+n", command: "terminal.new", when: "terminalFocus" },
{ key: "mod+w", command: "terminal.close", when: "terminalFocus" },
{ key: "mod+d", command: "diff.toggle", when: "!terminalFocus" },
{ key: "mod+k", command: "commandPalette.toggle", when: "!terminalFocus" },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's probably justification for triggering the command pallette even when the terminal is open. I doubt mod+k is gonna be a terminal keyboard shortcut, but I could see myself working in the terminal and wanting to navigate away and it being annoying to click out to focus the window

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah true, this wasn't something i've considered, as I have previously seen the two as two different states of interaction.

  1. Working with the ai and the threads
  2. Working with the terminal and running additional commands rather than asking ai to do it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main time I would run into it I think is if I'm working on two worktrees of the same thing (like T3 Code).

I will usually have my dev command running into the terminal on the worktree I was last testing, then I'll kill that command and want to navigate to the next worktree I need to check on and run the command there. Being able to Ctrl+K would be useful there (and also how I've been doing it in my command pallette branch)

Comment on lines +146 to +149
title: "New thread",
description: activeProjectTitle
? `Create a draft thread in ${activeProjectTitle}`
: "Create a new draft thread",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this description adds much beyond clutter tbh. I don't think I'm getting more info than just the title, and we could compact the UI a bit by removing the descriptions that aren't needed.

If the title was just "New draft thread in ${activeProjectTitle}" I think it would be more compact overall

The desc is useful for the thread list though, with the project being the desc

executeItem(item);
}}
>
<span className="flex size-8 shrink-0 items-center justify-center rounded-md border border-border/70 bg-muted/30">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some of the styling here could be compacted a bit to match the popup in the chat when you use /. Particularly this icon and the border around it, feels a bit weird for one place to have a box around the icon and another to not.

Image

@Noojuno
Copy link
Contributor

Noojuno commented Mar 15, 2026

@Noojuno This is good feedback. Thanks.

I was going to get this in, then allow for users to drag and drop in settings to re-organise the palette etc. Choose what they want to see etc. -- Was going to do this after so as to not bloat this pr.

That's fair, although honestly I feel like that level of customisation is probs overkill tbh. It could be useful, but I don't think I can think of a command pallette in an app where I can reorder sections, or a time where I have ever really looked for that option haha.

@huxcrux
Copy link
Contributor

huxcrux commented Mar 15, 2026

I really like this however have 2 suggestions

  1. I think when searching for threads there should be a timestamp on the right hand side,
image
  1. I would also like to search for projects (including their path), this could use the same create thread as being used in the sidebar
image

Comment on lines +217 to +221
const descriptionParts = [
projectTitle,
thread.branch ? `#${thread.branch}` : null,
thread.id === activeThread?.id ? "Current thread" : null,
].filter(Boolean);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If my Tray PR is ever merged, I might "steal" this as our canonical "description" for a thread. Unless we expect this to change frequently as we iterate.

Comment on lines +332 to +335
{item.description ? (
<span className="truncate text-muted-foreground/70 text-xs">
{item.description}
</span>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to truncate the description? Would it ever get too long?

value: `thread:${thread.id}`,
label: `${thread.title} ${projectTitle ?? ""} ${thread.branch ?? ""}`.trim(),
title: thread.title,
description: descriptionParts.join(" · "),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this character (or really any separator character) might not be super friendly to screen readers. Perhaps that could be fixed with ARIA.

@binbandit
Copy link
Contributor Author

binbandit commented Mar 15, 2026

@huxcrux ooft quality suggestions. Thank you!

U wanna contribute them to own them?

Reach out on discord and ill give you write access so then your name appears next to the feature 😄
Username: ._0siris_.

@eggfriedrice24
Copy link
Contributor

Heyyy, nice work! We can close 1076 if you consider adding add project functionality with autocompletion here, I mean we can close it regardless 🤣 but I would really love that feature.

@binbandit
Copy link
Contributor Author

Heyyy, nice work! We can close 1076 if you consider adding add project functionality with autocompletion here, I mean we can close it regardless 🤣 but I would really love that feature.

do you want to contribute that feature of it? So then your work / recognition doesn't get lost?
If so reach out on discord

@eggfriedrice24
Copy link
Contributor

eggfriedrice24 commented Mar 15, 2026

Heyyy, nice work! We can close 1076 if you consider adding add project functionality with autocompletion here, I mean we can close it regardless 🤣 but I would really love that feature.

do you want to contribute that feature of it? So then your work / recognition doesn't get lost? If so reach out on discord

Would love to! The filesystem browse endpoint and autocomplete logic from #1076 should fit right in, commits in there are detailed so we could cherry pick the relevant pieces. I'll reach out on Discord.

edit: @binbandit invite sent.

readonly items: ReadonlyArray<CommandPaletteItem>;
}

const CommandPaletteContext = createContext<CommandPaletteState | null>(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any thoughts on moving this out into zustand to match (most of) the rest of the app state?

Copy link
Contributor

@eggfriedrice24 eggfriedrice24 Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree

@github-actions github-actions bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Mar 15, 2026
@eggfriedrice24
Copy link
Contributor

eggfriedrice24 commented Mar 15, 2026

Added project browser to the command palette across contracts, server, WS transport, and frontend.

How it works: "Add project" action opens browse mode with ~/ pre-filled, showing home directory contents. Typing any path prefix (/, ~/, ./) activates browse mode with debounced directory listing. Tab drills into the highlighted directory, Enter adds it as a project. Browse mode is derived from the query prefix, no extra state. Browse items use the same executeItem/run flow as all other palette items.

I suggest checking individual commits rather then final diff. thanks!

@binbandit @Noojuno @huxcrux @nmggithub

edit: will upload video shortly, obs is fighting me for some reason

const { setOpen } = useCommandPalette();
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const isBrowsing = query.startsWith("/") || query.startsWith("~/") || query.startsWith("./");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should also have windows absolute path support. ex C:\code\repo?

@huxcrux
Copy link
Contributor

huxcrux commented Mar 15, 2026

Added project browser to the command palette across contracts, server, WS transport, and frontend.

How it works: "Add project" action opens browse mode with ~/ pre-filled, showing home directory contents. Typing any path prefix (/, ~/, ./) activates browse mode with debounced directory listing. Tab drills into the highlighted directory, Enter adds it as a project. Browse mode is derived from the query prefix, no extra state. Browse items use the same executeItem/run flow as all other palette items.

I suggest checking individual commits rather then final diff. thanks!

@binbandit @Noojuno @huxcrux @nmggithub

edit: will upload video shortly, obs is fighting me for some reason

I added one review already, but I have another. This might be nitpicking, but because path browsing is debounced by 200ms, pressing Enter before the results update can act on a stale path instead of the latest input.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants