areibman / areibman/bottleneck

Feature: Full issue management - change status, add labels, assign people, and more

Open
#65 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
156
Forks
21
PR merge metrics
No merged PRs in 30d

Description

## Feature Request

Add comprehensive issue management capabilities to allow users to perform all common issue operations directly from the app, including changing status, adding/removing labels, assigning people, setting milestones, and more.

## Description

Currently, users may need to go to GitHub to perform many issue management tasks. The app should provide full issue management capabilities similar to what's available on GitHub's web interface.

## Core Features

### 1. Issue Status Management

```typescript
interface IssueActions {
// Status changes
close: () => Promise;
reopen: () => Promise;
lock: (reason?: LockReason) => Promise;
unlock: () => Promise;
pin: () => Promise;
unpin: () => Promise;
markAsCompleted: () => Promise;
markAsNotPlanned: () => Promise;
convertToPR: () => Promise;
transferToRepo: (targetRepo: string) => Promise;
}

function IssueStatusControls({ issue }) {
return (


{/* Primary Status */}

Status


{issue.state === 'open' ? (

closeIssue(issue, 'completed')}
>
✅ Close as completed

closeIssue(issue, 'not_planned')}
>
❌ Close as not planned


) : (
reopenIssue(issue)}
>
🔄 Reopen issue

)}


{/* Lock/Unlock */}

{issue.locked ? (
unlockIssue(issue)}>
🔓 Unlock conversation

) : (

setShowLockDialog(true)}>
🔒 Lock conversation

{showLockDialog && (
lockIssue(issue, reason)}
reasons={[
'off-topic',
'too heated',
'resolved',
'spam'
]}
/>
)}

)}


{/* Pin/Unpin */}

issue.pinned ? unpinIssue(issue) : pinIssue(issue)}>
{issue.pinned ? '📌 Unpin issue' : '📍 Pin issue'}



);
}
```

### 2. Label Management

```typescript
interface LabelManager {
current: Label[];
available: Label[];
add: (labels: string[]) => Promise;
remove: (labels: string[]) => Promise;
create: (label: NewLabel) => Promise;
edit: (label: Label) => Promise;
}

function IssueLabelManager({ issue }) {
const [labels, setLabels] = useState(issue.labels);
const [showLabelPicker, setShowLabelPicker] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [creating, setCreating] = useState(false);

return (



Labels


setShowLabelPicker(!showLabelPicker)}
>
⚙️




{labels.map(label => (

{label.name}
removeLabel(label)}
>
×


))}

setShowLabelPicker(true)}
>
+ Add label



{showLabelPicker && (

setSearchTerm(e.target.value)}
className="label-search"
/>


{/* Suggested Labels */}

Suggested

{suggestedLabels.map(label => (

))}


{/* All Labels */}

All Labels

{filteredLabels.map(label => (

))}



{/* Create New Label */}

setCreating(true)}>
+ Create new label

{creating && (
setCreating(false)}
/>
)}



Apply labels
setShowLabelPicker(false)}>Cancel


)}

);
}
```

### 3. Assignment Management

```typescript
interface AssigneeManager {
current: User[];
suggestions: User[];
recentAssignees: User[];
add: (users: User[]) => Promise;
remove: (users: User[]) => Promise;
requestReview: (user: User) => Promise;
}

function IssueAssignees({ issue }) {
const [assignees, setAssignees] = useState(issue.assignees);
const [showPicker, setShowPicker] = useState(false);
const [searchTerm, setSearchTerm] = useState('');

return (



Assignees


setShowPicker(!showPicker)}>
⚙️




{assignees.length === 0 ? (
No one assigned
) : (
assignees.map(user => (

{user.login}
{user.login}
removeAssignee(user)}>×

))
)}

setShowPicker(true)}
>
+ Add assignees



{showPicker && (
{
addAssignees(users);
setShowPicker(false);
}}
suggestions={[
{ title: 'Suggested', users: suggestedAssignees },
{ title: 'Recent', users: recentAssignees },
{ title: 'Team', users: teamMembers }
]}
/>
)}

{/* Quick Actions */}

assignToMe()}>
Assign myself

clearAllAssignees()}>
Clear assignees



);
}
```

### 4. Project and Milestone Management

```typescript
function IssueProjectsAndMilestones({ issue }) {
return (


{/* Projects */}

Projects



{issue.projects.map(project => (

{project.name}
{project.column}
removeFromProject(project)}>×

))}


addToProject(project, column)}
/>


{/* Milestone */}

Milestone


{issue.milestone ? (

{issue.milestone.title}

{issue.milestone.closed_issues}/{issue.milestone.open_issues + issue.milestone.closed_issues}

removeMilestone()}>×

) : (
No milestone
)}




);
}
```

### 5. Bulk Operations

```typescript
interface BulkOperations {
selectedIssues: Issue[];
actions: {
close: () => Promise;
reopen: () => Promise;
addLabels: (labels: string[]) => Promise;
removeLabels: (labels: string[]) => Promise;
assign: (users: User[]) => Promise;
setMilestone: (milestone: Milestone) => Promise;
archive: () => Promise;
};
}

function IssueBulkActions({ selectedIssues }) {
if (selectedIssues.length === 0) return null;

return (


{selectedIssues.length} issues selected


showBulkEditDialog('status')}>
📝 Change status


showBulkEditDialog('labels')}>
🏷️ Edit labels


showBulkEditDialog('assignees')}>
👥 Assign


showBulkEditDialog('milestone')}>
🎯 Set milestone


showBulkEditDialog('project')}>
📊 Add to project




Clear selection


);
}
```

### 6. Issue Templates and Quick Actions

```typescript
function IssueQuickActions({ issue }) {
return (


convertToDiscussion(issue)}>
💬 Convert to discussion


createBranchFromIssue(issue)}>
🌿 Create branch


generatePRTemplate(issue)}>
📝 Generate PR from issue


linkPR(issue)}>
🔗 Link pull request


duplicateIssue(issue)}>
📋 Duplicate issue


moveToRepo(issue)}>
📦 Transfer to another repo


createSubIssues(issue)}>
➕ Create sub-issues


exportIssue(issue)}>
💾 Export as markdown


);
}
```

### 7. Issue Sidebar (GitHub-like)

```typescript
function IssueSidebar({ issue }) {
return (







Notifications






Participants






Development







Timeline


showTimeline(issue)}>
View timeline




Danger Zone


deleteIssue(issue)}>
🗑️ Delete issue



);
}
```

### 8. API Implementation

```typescript
class IssueManager {
async updateIssue(issueNumber: number, updates: IssueUpdate) {
return await this.octokit.issues.update({
owner: this.owner,
repo: this.repo,
issue_number: issueNumber,
...updates
});
}

async addLabels(issueNumber: number, labels: string[]) {
return await this.octokit.issues.addLabels({
owner: this.owner,
repo: this.repo,
issue_number: issueNumber,
labels
});
}

async removeLabel(issueNumber: number, label: string) {
return await this.octokit.issues.removeLabel({
owner: this.owner,
repo: this.repo,
issue_number: issueNumber,
name: label
});
}

async addAssignees(issueNumber: number, assignees: string[]) {
return await this.octokit.issues.addAssignees({
owner: this.owner,
repo: this.repo,
issue_number: issueNumber,
assignees
});
}

async setMilestone(issueNumber: number, milestoneNumber: number) {
return await this.octokit.issues.update({
owner: this.owner,
repo: this.repo,
issue_number: issueNumber,
milestone: milestoneNumber
});
}

async lockIssue(issueNumber: number, reason?: string) {
return await this.octokit.issues.lock({
owner: this.owner,
repo: this.repo,
issue_number: issueNumber,
lock_reason: reason
});
}
}
```

### 9. Keyboard Shortcuts

```typescript
const issueKeyboardShortcuts = {
'c': 'Close/Open issue',
'l': 'Add labels',
'a': 'Add assignee',
'm': 'Set milestone',
'p': 'Add to project',
'shift+d': 'Duplicate issue',
'shift+t': 'Transfer issue',
'cmd+enter': 'Submit comment',
'e': 'Edit issue title/description',
'x': 'Select/deselect issue (in list view)',
'shift+x': 'Select range'
};
```

### 10. Real-time Updates

```typescript
class IssueRealtimeSync {
subscribeToIssue(issueNumber: number) {
// WebSocket subscription for real-time updates
this.ws.subscribe(`issue:${issueNumber}`, (event) => {
switch (event.type) {
case 'labeled':
this.handleLabelAdded(event);
break;
case 'unlabeled':
this.handleLabelRemoved(event);
break;
case 'assigned':
this.handleAssigned(event);
break;
case 'closed':
this.handleClosed(event);
break;
// ... other events
}
});
}
}
```

## Benefits

- **Full GitHub parity**: All issue management features in one place
- **Improved workflow**: No need to switch to GitHub for common tasks
- **Bulk operations**: Manage multiple issues efficiently
- **Better UX**: Optimized interface for issue management
- **Keyboard driven**: Fast operations with shortcuts
- **Real-time sync**: See changes as they happen

## Acceptance Criteria

- [ ] Can close and reopen issues
- [ ] Can add/remove labels
- [ ] Can create new labels
- [ ] Can assign/unassign people
- [ ] Can set/clear milestones
- [ ] Can add issues to projects
- [ ] Can lock/unlock conversations
- [ ] Can pin/unpin issues
- [ ] Bulk operations work for multiple issues
- [ ] Changes sync in real-time
- [ ] Keyboard shortcuts work
- [ ] Error handling for permission issues
- [ ] Undo/redo for recent actions
- [ ] All changes reflected immediately in UI
- [ ] Works with different repository permissions

## Future Enhancements

- Issue templates support
- Custom fields
- Time tracking
- Issue dependencies
- Automated workflows
- AI-powered label suggestions
- Issue analytics
- Saved filters and views

🤖 Generated with [Claude Code](https://claude.ai/code)

Contributor guide

No contributing guide indexed for this repository

Research direction

The issue names no repository files, tests, or entry points. Review the requested IssueStatusControls, IssueLabelManager, IssueAssignees, IssueProjectsAndMilestones, IssueBulkActions, and IssueQuickActions concepts, then narrow the work into a defined subset. Done is not specified beyond providing the listed GitHub-like operations.

Written by the indexing model from the issue text.

Assessment

Tech stack
electron, typescript
Domain
developer-experience, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.