feat(graph): add Assign button and archetype dropdown to GraphNodeCard

## The Collaboration Story
User requested ability to assign agent archetypes to tasks directly from
graph nodes. This was the core UI feature request.

## Design Decisions Made Together
1. **Placement**: We decided to put the assign UI at the bottom of the card
   to not interfere with existing status/badges display
2. **Pattern**: Used Radix dropdown-menu (already in project) for consistency
3. **Visual feedback**: Added loading spinner during API calls, success/error
   messages that auto-dismiss
4. **Closed tasks**: Excluded closed tasks from assignment (logical constraint)

## Issues We Encountered
- Initially only added POST handler for assignment
- User pointed out we needed DELETE for unassign - added that
- The unassign button (X) needed to call DELETE not POST

## API Changes
- Added DELETE handler to /api/swarm/prep for removing agent assignments
- Uses 'bd label remove' under the hood

## What the UI Shows
- Dropdown with available archetypes
- Badge showing assigned archetype name with color
- X button to unassign (on each badge)
- 'Assigned' label on already-assigned archetypes in dropdown

## Test Coverage
- Added graph-node-assign.test.tsx with 6 TDD tests

## Beads: beadboard-brq (closed)
This commit is contained in:
zenchantlive 2026-02-24 16:14:56 -08:00
parent 164b26e570
commit 512a836db4
3 changed files with 297 additions and 19 deletions

View file

@ -17,7 +17,7 @@ export async function POST(request: Request) {
const { stdout, stderr } = await execAsync(cmd);
if (stderr && !stderr.includes('Warning')) {
console.error('bd edit stderr:', stderr);
console.error('bd label add stderr:', stderr);
}
return NextResponse.json({ success: true, message: `Prepped ${beadId} for ${archetypeId}`, output: stdout });
@ -27,3 +27,29 @@ export async function POST(request: Request) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function DELETE(request: Request) {
try {
const { beadId, archetypeId } = await request.json();
if (!beadId) {
return NextResponse.json({ error: 'Missing beadId' }, { status: 400 });
}
// Remove the agent: label
// If archetypeId is provided, remove specific label, otherwise remove any agent: label
const labelToRemove = archetypeId ? `agent:${archetypeId}` : 'agent:';
const cmd = `bd label remove ${beadId} "${labelToRemove}"`;
const { stdout, stderr } = await execAsync(cmd);
if (stderr && !stderr.includes('Warning')) {
console.error('bd label remove stderr:', stderr);
}
return NextResponse.json({ success: true, message: `Removed assignment from ${beadId}`, output: stdout });
} catch (error: any) {
console.error('Remove assignment failed:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}