Skip to content

feat(code): project pickers on quill autocomplete#2103

Open
adamleithp wants to merge 1 commit intoux/file-picker-quillfrom
ux/project-pickers-quill
Open

feat(code): project pickers on quill autocomplete#2103
adamleithp wants to merge 1 commit intoux/file-picker-quillfrom
ux/project-pickers-quill

Conversation

@adamleithp
Copy link
Copy Markdown
Contributor

@adamleithp adamleithp commented May 8, 2026

Summary

  • Migrate ProjectSwitcher (sidebar "Change project" dialog) and ProjectSelect (onboarding inline picker) from cmdk (via the Command.tsx wrapper) to quill Autocomplete*.
  • ProjectSwitcher now uses quill Dialog + DialogContent (matches CommandMenu / FilePicker); orgs render as labeled groups when there are 2+ orgs, otherwise unlabeled.
  • ProjectSelect keeps the Radix Popover host (anchored to the inline "change" trigger), Autocomplete is rendered inline inside.
  • Both: inline + defaultOpen + autoHighlight="always" pattern, custom filter for case-insensitive name matching, AutocompleteStatus empty content, CommandKeyHints footer on the dialog variant.
  • Delete the now-dead Command.tsx wrapper + Command.css, plus the cmdk-targeted ProjectSwitcher.css / ProjectSelect.css.

Stacked on #2101 (ux/file-picker-quill) which is stacked on #2100 (ux/cmd-menu). Merge order: #2100#2101 → this.

2026-05-08 08 14 41

What's left on cmdk

  • components/ui/combobox/Combobox.tsx + useComboboxFilter.ts (standalone reusable Combobox)
  • features/folder-picker/components/GitHubRepoPicker.tsx (only imports defaultFilter from cmdk)

Both can move to a follow-up PR (likely against quill's Combobox* namespace, separate from Autocomplete*).

Test plan

ProjectSwitcher (sidebar bottom-left → "Change project")

  • Click avatar/project chip → dropdown opens.
  • Click "Change project" → dialog opens, input autofocused, first project highlighted (autoHighlight=always).
  • Type a partial project name → list filters case-insensitively; "X results" status updates.
  • Empty-search status renders nothing (it's the result count); No projects match "xyz" shows when filter is empty.
  • When the user belongs to multiple orgs: each org renders as a labeled group; with one org, no header.
  • Current project shows ✓ icon next to its name.
  • ↑/↓ moves highlight, Enter selects → mutation fires, dialog closes.
  • Clicking a project selects it.
  • Esc closes the dialog in a single press.
  • Reopening the dialog clears the previous query.

ProjectSelect (onboarding "change" link next to project name)

  • Visible only when there are 2+ projects available (single project: plain text, no trigger).
  • Click "change" → popover opens below, input autofocused.
  • Type → filters case-insensitively.
  • ↑/↓ + Enter selects, popover closes, onProjectChange fires with the picked id.
  • Clicking outside the popover closes it.
  • Esc closes in a single press.
  • Disabled state: disabled prop dims the trigger and prevents clicks.

Regressions / general

  • CommandMenu (Cmd+K) still works.
  • FilePicker (Cmd+P) still works.
  • Sidebar dropdown menu (Settings, Logout, Discord, Learn more, etc.) renders normally — only the "Change project" path was touched.
  • pnpm --filter code typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 8, 2026

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/code/src/renderer/features/sidebar/components/ProjectSwitcher.tsx:286-291
Query not reset when a project is selected via click or Enter. `handleProjectSelect` calls `setDialogOpen(false)` directly on the parent state, bypassing `handleOpenChange`, so `setQuery("")` is never reached. Compare with `FilePicker.handleSelect` which explicitly calls `handleOpenChange(false)`. The test plan says "Reopening the dialog clears the previous query" — that only holds for Escape/overlay closes today, not for project selection.

```suggestion
  const handleSelect = (id: string | null) => {
    if (id === null) return;
    const projectId = Number(id);
    if (Number.isNaN(projectId)) return;
    handleProjectSelect(projectId);
    setQuery("");
  };
```

### Issue 2 of 2
apps/code/src/renderer/features/onboarding/components/ProjectSelect.tsx:40-46
Same issue as `ProjectSwitcher`: when a project is selected, `setOpen(false)` changes the controlled `open` state directly, so Radix UI's Popover does not fire `onOpenChange` and `setQuery("")` is never reached. If the user re-opens the picker after a selection, the previous search string is still visible. Routing through `handleOpenChange` ensures the query is always cleared on any close path.

```suggestion
  const handleSelect = (id: string | null) => {
    if (id === null) return;
    const next = Number(id);
    if (Number.isNaN(next)) return;
    onProjectChange(next);
    handleOpenChange(false);
  };
```

Reviews (1): Last reviewed commit: "feat(code): project pickers on quill aut..." | Re-trigger Greptile

Comment on lines +286 to +291
const handleSelect = (id: string | null) => {
if (id === null) return;
const projectId = Number(id);
if (Number.isNaN(projectId)) return;
handleProjectSelect(projectId);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Query not reset when a project is selected via click or Enter. handleProjectSelect calls setDialogOpen(false) directly on the parent state, bypassing handleOpenChange, so setQuery("") is never reached. Compare with FilePicker.handleSelect which explicitly calls handleOpenChange(false). The test plan says "Reopening the dialog clears the previous query" — that only holds for Escape/overlay closes today, not for project selection.

Suggested change
const handleSelect = (id: string | null) => {
if (id === null) return;
const projectId = Number(id);
if (Number.isNaN(projectId)) return;
handleProjectSelect(projectId);
};
const handleSelect = (id: string | null) => {
if (id === null) return;
const projectId = Number(id);
if (Number.isNaN(projectId)) return;
handleProjectSelect(projectId);
setQuery("");
};
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/sidebar/components/ProjectSwitcher.tsx
Line: 286-291

Comment:
Query not reset when a project is selected via click or Enter. `handleProjectSelect` calls `setDialogOpen(false)` directly on the parent state, bypassing `handleOpenChange`, so `setQuery("")` is never reached. Compare with `FilePicker.handleSelect` which explicitly calls `handleOpenChange(false)`. The test plan says "Reopening the dialog clears the previous query" — that only holds for Escape/overlay closes today, not for project selection.

```suggestion
  const handleSelect = (id: string | null) => {
    if (id === null) return;
    const projectId = Number(id);
    if (Number.isNaN(projectId)) return;
    handleProjectSelect(projectId);
    setQuery("");
  };
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +40 to +46
const handleSelect = (id: string | null) => {
if (id === null) return;
const next = Number(id);
if (Number.isNaN(next)) return;
onProjectChange(next);
setOpen(false);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Same issue as ProjectSwitcher: when a project is selected, setOpen(false) changes the controlled open state directly, so Radix UI's Popover does not fire onOpenChange and setQuery("") is never reached. If the user re-opens the picker after a selection, the previous search string is still visible. Routing through handleOpenChange ensures the query is always cleared on any close path.

Suggested change
const handleSelect = (id: string | null) => {
if (id === null) return;
const next = Number(id);
if (Number.isNaN(next)) return;
onProjectChange(next);
setOpen(false);
};
const handleSelect = (id: string | null) => {
if (id === null) return;
const next = Number(id);
if (Number.isNaN(next)) return;
onProjectChange(next);
handleOpenChange(false);
};
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/onboarding/components/ProjectSelect.tsx
Line: 40-46

Comment:
Same issue as `ProjectSwitcher`: when a project is selected, `setOpen(false)` changes the controlled `open` state directly, so Radix UI's Popover does not fire `onOpenChange` and `setQuery("")` is never reached. If the user re-opens the picker after a selection, the previous search string is still visible. Routing through `handleOpenChange` ensures the query is always cleared on any close path.

```suggestion
  const handleSelect = (id: string | null) => {
    if (id === null) return;
    const next = Number(id);
    if (Number.isNaN(next)) return;
    onProjectChange(next);
    handleOpenChange(false);
  };
```

How can I resolve this? If you propose a fix, please make it concise.

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