-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Summary
Add a "Move to Another Board" menu item to the RepoCard overflow menu (OverflowMenu.tsx), allowing users to transfer a card from the current board to a different board's status column — without deleting and re-adding.
Motivation
Currently users must:
- Remove from Board (permanently deletes the card + projectinfo)
- Navigate to the target board
- Re-add the repository via AddRepositoryCombobox
- Re-enter notes, links, and comments from scratch
This is destructive and tedious. A direct move operation preserves all projectinfo (notes, links, comments) while changing only board_id and status_id.
User Story
As a user managing repositories across multiple boards, I want to move a card to another board so that my project notes and links are preserved.
UI Design
OverflowMenu Addition
┌──────────────────────────┐
│ ☐ Open on GitHub │
│ ☐ Open Production URL │
│ ──────────────────── │
│ 📦 Move to Maintenance │
│ 📋 Move to Another Board │ ← NEW
│ ──────────────────── │
│ 🗑 Remove from Board │
└──────────────────────────┘
- Position: After "Move to Maintenance", before the destructive separator
- Icon:
ArrowRightLeftfrom lucide-react - Label: "Move to Another Board"
- Context:
boardonly (not shown in maintenance context)
Board Picker Dialog
When clicked, opens a Dialog with a two-step selector:
┌─────────────────────────────────────────┐
│ Move to Another Board │
│ ─────────────────────────────────────── │
│ │
│ Select destination board: │
│ ┌─────────────────────────────────────┐ │
│ │ 🔲 Work Projects │ │
│ │ 🔲 Personal OSS │ │
│ │ 🔲 Learning & Research │ │
│ └─────────────────────────────────────┘ │
│ │
│ Select column: │
│ ┌─────────────────────────────────────┐ │
│ │ 🟢 Pending │ │
│ │ 🟡 In Progress │ │
│ │ 🔵 Done │ │
│ └─────────────────────────────────────┘ │
│ │
│ ─────────────────────────────────────── │
│ [Cancel] [Move] │
└─────────────────────────────────────────┘
Behavior:
- Current board is excluded from the board list
- Board selection auto-selects the first column of that board
- Column list shows colored dots matching column
color - Move button is disabled until both board and column are selected
- On success: card disappears from current board (optimistic), toast "Moved to {boardName}"
- On error: toast with error message, card remains in place
Architecture
Files to Create / Modify
| File | Action | Description |
|---|---|---|
supabase/migrations/YYYYMMDD_move_card_to_board.sql |
Create | RPC function move_card_to_board |
src/lib/actions/repo-cards.ts |
Modify | Add moveCardToBoard() server action |
src/components/Board/MoveToAnotherBoardDialog.tsx |
Create | Board + column picker dialog |
src/components/Board/OverflowMenu.tsx |
Modify | Add menu item + dialog trigger |
src/components/Board/RepoCard.tsx |
Modify | Pass onMoveToBoard callback |
src/components/Board/KanbanBoard.tsx |
Modify | Handle card removal after move |
src/app/board/[id]/BoardPageClient.tsx |
Modify | Wire handleMoveToBoard callback |
e2e/logged-in/move-to-another-board.spec.ts |
Create | E2E tests |
src/tests/unit/components/Board/MoveToAnotherBoardDialog.test.tsx |
Create | Unit tests |
Database: RPC Function
CREATE OR REPLACE FUNCTION move_card_to_board(
p_card_id UUID,
p_target_board_id UUID,
p_target_status_id UUID
)
RETURNS VOID
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = pg_catalog, public
AS $$
DECLARE
v_next_order INTEGER;
BEGIN
-- Calculate next order in target status column
SELECT COALESCE(MAX("order"), -1) + 1 INTO v_next_order
FROM public.repocard
WHERE status_id = p_target_status_id;
-- Atomic update: change board_id, status_id, order
UPDATE public.repocard
SET board_id = p_target_board_id,
status_id = p_target_status_id,
"order" = v_next_order,
updated_at = now()
WHERE id = p_card_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'repocard % not found', p_card_id;
END IF;
END;
$$;Key design decisions:
- UPDATE, not DELETE+INSERT: Preserves
repocard.id→projectinfo.repo_card_idFK stays valid automatically SECURITY INVOKERrespects RLS (user must own both boards)- UNIQUE constraint
(board_id, repo_owner, repo_name)will naturally raise an error if duplicate exists → catch in server action
Server Action
// lib/actions/repo-cards.ts
export async function moveCardToBoard(
cardId: string,
targetBoardId: string,
targetStatusId: string,
): Promise<ActionResult<{ cardId: string }>>Validation:
- Validate all 3 UUIDs via
uuidSchema - Verify target board ownership (RLS handles this, but explicit check for better error message)
- Verify target status belongs to target board
- Check for duplicate
(board_id, repo_owner, repo_name)before calling RPC - Call
move_card_to_boardRPC - Return
{ success: true, data: { cardId } }
Component: MoveToAnotherBoardDialog
interface MoveToAnotherBoardDialogProps {
isOpen: boolean
onClose: () => void
cardId: string
cardTitle: string
currentBoardId: string
onMoveSuccess: (cardId: string) => void
}State flow:
- On open → call
getUserBoardsWithStatusLists()(lazy load) - Filter out
currentBoardIdfrom board list - User selects board → populate column list
- User selects column → enable Move button
- On Move → call
moveCardToBoard(cardId, boardId, statusId) - On success →
onMoveSuccess(cardId), close dialog, toast
Redux Integration
In BoardPageClient.tsx:
const handleMoveToBoard = useCallback(async (cardId: string) => {
// After successful move, remove card from current board's Redux state
dispatch(removeRepoCard(cardId))
}, [dispatch])Acceptance Criteria
- "Move to Another Board" appears in OverflowMenu (board context only)
- Dialog shows all user's boards except the current one
- Dialog shows status columns for the selected board with color indicators
- Move button is disabled until both board and column are selected
- Successful move: card disappears from current board, toast confirmation
- ProjectInfo (notes, links, comments) is preserved after move
- Duplicate detection: shows error if repo already exists on target board
- Loading state during board fetch and move operation
- Works with keyboard navigation (tab through boards, columns, Move button)
- Accessibility: proper ARIA labels, focus management
- E2E test: move card → verify card on target board → verify projectinfo preserved
- Unit test: dialog rendering, selection state, error handling
Edge Cases
| Case | Expected Behavior |
|---|---|
| User has only 1 board | Menu item disabled with tooltip "No other boards available" |
| Target board has same repo | Error toast: "Repository already exists on {boardName}" |
| Board deleted while dialog open | Error toast: "Board not found" |
| Network error during move | Error toast, card stays on current board |
| Card moved while dialog open (race) | RPC raises error, toast notification |
Test Plan
E2E (e2e/logged-in/move-to-another-board.spec.ts)
- Menu item visible in overflow menu
- Dialog opens with board list (current board excluded)
- Column list populates on board selection
- Successful move → card removed from source board
- Navigate to target board → card visible in correct column
- ProjectInfo preserved after move (verify via DB query)
- Duplicate prevention (add same repo to target board first, then try to move)
Unit Tests
- Dialog rendering (open/close, board list, column list)
- Board filtering (current board excluded)
- Selection state (disabled button, enabled after selection)
- Error state rendering
- Loading state rendering
Dependencies
- Existing:
getUserBoardsWithStatusLists()(repo-cards.ts:427) - Existing:
ActionResult<T>pattern - Existing:
OverflowMenucomponent - New:
move_card_to_boardRPC function (migration)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request