Skip to content

feat(task-modal): add project selection via number keys#1390

Open
drochag wants to merge 2 commits intogeneralaction:mainfrom
drochag:feat/task-modal-project-selection
Open

feat(task-modal): add project selection via number keys#1390
drochag wants to merge 2 commits intogeneralaction:mainfrom
drochag:feat/task-modal-project-selection

Conversation

@drochag
Copy link

@drochag drochag commented Mar 10, 2026

Summary

  • Add the ability to select a project using number keys (1-9) when the TaskModal is open via Cmd+N
  • Previously, tasks were always created in the currently active project with no way to change it from the modal
  • When multiple projects exist, shows an inline project selector with number badges

Test plan

  • Press Cmd+N with multiple projects - selector should appear
  • Press number keys 1-9 - should select corresponding project
  • Click on a project - should select it
  • Selected project should show checkmark
  • Branch dropdown should update when project changes
  • Create task - should be created in selected project
  • With single project - should show old UI (no selector)
  • While typing in task name input - number keys should not select projects

Closes #1376

Add the ability to select a project using number keys (1-9) when the
TaskModal is open via Cmd+N. Previously, tasks were always created in
the currently active project with no way to change it from the modal.

Changes:
- Add inline project selector to TaskModal header showing all projects
  with number badges (1-9)
- Allow number key selection when no input field is focused
- Pre-select the current project by default
- Update branch options when project changes
- Pass selected project to handleCreateTask

Closes generalaction#1376

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 10, 2026

@drochag is attempting to deploy a commit to the General Action Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR adds a project-selector UI to the TaskModal, allowing users to pick a target project via number keys (1–9) or mouse click when multiple projects exist. The handleCreateTask hook is updated to accept an explicit project argument so the modal's selection is honoured at creation time.

Key changes and issues found:

  • New selectedModalProject local state — the modal now maintains its own project selection independent of the global selectedProject, with local branch-loading logic for non-context projects.
  • refreshBranches removed from mount — the explicit branch-refresh on mount was dropped; the context is now expected to keep branches current, which appears safe given how the context is structured.
  • Race condition in loadBranches (see inline comment) — multiple in-flight branch-load requests can resolve out of order when the user switches projects quickly, causing the branch dropdown to show stale data for the wrong project.
  • Missing project navigation on task creation (see inline comment) — when a task is created for a project that is not currently active, setSelectedProject is never called, leaving activeTask.projectId pointing at the new project while selectedProject remains the old one. The existing sidebar flow avoids this via activateProjectView before the modal opens, but the new in-modal selection has no equivalent.

Confidence Score: 2/5

  • Not safe to merge — two logic bugs can leave the UI in an inconsistent state or display branches for the wrong project.
  • The feature works correctly in the happy path (single fast project selection), but the race condition in branch loading and the missing project navigation on cross-project task creation are both reproducible defects that affect real-world usage. The navigation bug in particular puts the app in a state where activeTask and selectedProject are out of sync, which is likely to cascade into other UI issues.
  • src/renderer/components/TaskModal.tsx (branch-load race condition) and src/renderer/hooks/useTaskManagement.ts (missing setSelectedProject call) both require attention before merging.

Important Files Changed

Filename Overview
src/renderer/components/TaskModal.tsx Adds inline project selector with number-key shortcuts and local branch loading. Contains a race condition in concurrent branch-load calls when the user switches projects quickly.
src/renderer/hooks/useTaskManagement.ts Adds optional project parameter to handleCreateTask and uses it as the primary target. Missing setSelectedProject call when creating a task for a non-active project, leaving UI in inconsistent state.

Sequence Diagram

sequenceDiagram
    participant User
    participant TaskModal
    participant ProjectManagementContext
    participant useTaskManagement
    participant ElectronAPI

    User->>TaskModal: Opens modal (Cmd+N)
    TaskModal->>ProjectManagementContext: reads projects, selectedProject, contextBranchOptions
    TaskModal->>TaskModal: useState(selectedModalProject = selectedProject)

    User->>TaskModal: Presses number key OR clicks project
    TaskModal->>TaskModal: setSelectedModalProject(projects[index])

    alt selectedModalProject === selectedProject (context project)
        TaskModal->>TaskModal: uses contextBranchOptions
    else different project selected
        TaskModal->>ElectronAPI: listRemoteBranches / sshExecuteCommand
        ElectronAPI-->>TaskModal: branch list
        TaskModal->>TaskModal: setLocalBranchOptions(...)
    end

    User->>TaskModal: Submits form
    TaskModal->>useTaskManagement: handleCreateTask(..., selectedModalProject)
    useTaskManagement->>useTaskManagement: targetProject = project (selectedModalProject)
    Note over useTaskManagement: ⚠️ setSelectedProject NOT called if project ≠ selectedProject
    useTaskManagement->>useTaskManagement: createTaskMutation.mutateAsync(targetProject)
    useTaskManagement->>useTaskManagement: setActiveTask(optimisticTask) for targetProject
    Note over useTaskManagement: selectedProject still points to old project → inconsistent state
Loading

Comments Outside Diff (1)

  1. src/renderer/hooks/useTaskManagement.ts, line 906-941 (link)

    Missing project navigation when creating task for a non-active project

    When a task is created via the new project selector for a project that isn't the currently active one, handleCreateTask correctly uses the passed project argument as targetProject, but neither handleCreateTask nor createTaskMutation.onMutate/onSuccess calls setSelectedProject(targetProject).

    As a result, the app ends up in an inconsistent state:

    • activeTask.projectId points to the newly created task's project (B)
    • selectedProject remains the previously active project (A)

    This means the sidebar still shows project A's task list while the main view attempts to render a task belonging to project B. Compare this to the existing sidebar flow (handleStartCreateTaskFromSidebar), which explicitly calls activateProjectView(targetProject) before opening the modal to pre-switch the project.

    A setSelectedProject(targetProject) call (or equivalent navigation) should be made in handleCreateTask when the provided project differs from selectedProject:

    const targetProject = project || pendingTaskProjectRef.current || selectedProject;
    pendingTaskProjectRef.current = null;
    if (!targetProject) return;
    
    // Navigate to the target project if it differs from the currently selected one
    if (targetProject.id !== selectedProject?.id) {
      setSelectedProject(targetProject);
    }
    setIsCreatingTask(true);
    // ...

Last reviewed commit: 5657f80

Comment on lines +352 to +401
const loadBranches = async () => {
setLocalIsLoadingBranches(true);
const initialBranch = selectedModalProject.gitInfo?.baseRef || 'main';
setLocalBranchOptions([{ value: initialBranch, label: initialBranch }]);

try {
let options: { value: string; label: string }[];

if (selectedModalProject.isRemote && selectedModalProject.sshConnectionId) {
const result = await window.electronAPI.sshExecuteCommand(
selectedModalProject.sshConnectionId,
'git branch -a --format="%(refname:short)"',
selectedModalProject.path
);
if (result.exitCode === 0 && result.stdout) {
const branches = result.stdout
.split('\n')
.map((b) => b.trim())
.filter((b) => b.length > 0 && !b.includes('HEAD'));
options = branches.map((b) => ({ value: b, label: b }));
} else {
options = [];
}
} else {
const res = await window.electronAPI.listRemoteBranches({
projectPath: selectedModalProject.path,
});
if (res.success && res.branches) {
options = res.branches.map((b) => ({
value: b.ref,
label: b.remote ? b.label : `${b.branch} (local)`,
}));
} else {
options = [];
}
}

if (options.length > 0) {
setLocalBranchOptions(options);
}
} catch (error) {
console.error('Failed to load branches:', error);
} finally {
setLocalIsLoadingBranches(false);
}
};

userChangedBranchRef.current = false;
setSelectedBranch(selectedModalProject.gitInfo?.baseRef || 'main');
void loadBranches();
Copy link
Contributor

Choose a reason for hiding this comment

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

Race condition in branch loading

When a user rapidly clicks through projects, multiple loadBranches calls can run concurrently. Because there's no cancellation mechanism, whichever async call finishes last will overwrite localBranchOptions—even if it belongs to a project that's no longer selected. This means the branch dropdown could show branches for the wrong project.

For example:

  1. User clicks Project A → loadBranches(A) starts
  2. User quickly clicks Project B → loadBranches(B) starts
  3. B finishes first → localBranchOptions = B branches ✓
  4. A finishes last → localBranchOptions = A branches ✗ (wrong project is now displayed)

Fix by capturing the project ID at the start of the async function and skipping the state update if it no longer matches selectedModalProject?.id:

const loadBranches = async () => {
  const projectId = selectedModalProject.id; // capture at start
  setLocalIsLoadingBranches(true);
  // ... async work ...
  // After await, check if still relevant:
  if (selectedModalProject.id !== projectId) return;
  setLocalBranchOptions(options);
  setLocalIsLoadingBranches(false);
};

- Fix race condition in branch loading when rapidly switching projects
  by tracking current project ID in a ref and skipping stale updates
- Fix missing project navigation when creating task for non-active project
  by calling setSelectedProject when targetProject differs from current

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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.

Feature: Allow project selection via Cmd+N then number keys

1 participant