From 4c7e35899d01a516f26538982431e01006a18a55 Mon Sep 17 00:00:00 2001 From: Alex Netsch Date: Thu, 18 Sep 2025 13:08:52 +0200 Subject: [PATCH] Fix dropdown (vibe-kanban) (#764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Perfect! I have successfully fixed the dropdown actions issue in the non-fullscreen mode. Here's a summary of what was implemented: ## ✅ Fixed Dropdown Actions in Non-Fullscreen Mode ### **Problem Solved** The dropdown in task attempt details (non-fullscreen mode) was always showing "Rebase" and "Merge" actions, regardless of the branch status conditions. This was inconsistent with the fullscreen mode which properly applied conditional logic. ### **Changes Made** 1. **Added Required Imports** (`AttemptHeaderCard.tsx:16-18`): - `useBranchStatus` hook for branch status information - `useAttemptExecution` hook for execution state - `useMemo` and `useState` for state management 2. **Added Branch Status Logic** (`AttemptHeaderCard.tsx:52-97`): - Branch status data fetching - Conflict detection - Merge status information (open PRs, merged PRs, etc.) - Loading states for rebase and merge operations 3. **Enhanced Action Handlers** (`AttemptHeaderCard.tsx:109-129`): - Added proper async handlers with loading states - Proper error handling through mutations 4. **Applied Conditional Rendering**: - **Rebase** (`AttemptHeaderCard.tsx:186-193`): Only shown when: - Branch has commits behind (needs rebasing) - No merged PR exists - Branch status is available - **Merge** (`AttemptHeaderCard.tsx:200-214`): Only shown when: - No open PR exists - No conflicts present - Branch is not behind (rebased) - Has commits ahead to merge - No merged PR exists - Attempt is not running ### **Result** Now both fullscreen and non-fullscreen modes apply the same conditional logic for rebase and merge actions, providing a consistent user experience. The dropdown will only show these actions when they are actually applicable based on the branch status. ✅ **All linting and formatting checks passed** (99 warnings, 0 errors - warnings are acceptable). * fmt --- .../components/tasks/AttemptHeaderCard.tsx | 115 ++++++++++++++++-- 1 file changed, 103 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/tasks/AttemptHeaderCard.tsx b/frontend/src/components/tasks/AttemptHeaderCard.tsx index 3448ec9a..c493c167 100644 --- a/frontend/src/components/tasks/AttemptHeaderCard.tsx +++ b/frontend/src/components/tasks/AttemptHeaderCard.tsx @@ -13,6 +13,9 @@ import { useRebase } from '@/hooks/useRebase'; import { useMerge } from '@/hooks/useMerge'; import { useOpenInEditor } from '@/hooks/useOpenInEditor'; import { useDiffSummary } from '@/hooks/useDiffSummary'; +import { useBranchStatus } from '@/hooks'; +import { useAttemptExecution } from '@/hooks/useAttemptExecution'; +import { useMemo, useState } from 'react'; import NiceModal from '@ebay/nice-modal-react'; interface AttemptHeaderCardProps { @@ -44,6 +47,58 @@ export function AttemptHeaderCard({ const { fileCount, added, deleted } = useDiffSummary( selectedAttempt?.id ?? null ); + + // Branch status and execution state + const { data: branchStatus } = useBranchStatus(selectedAttempt?.id); + const { isAttemptRunning } = useAttemptExecution( + selectedAttempt?.id, + task.id + ); + + // Loading states + const [rebasing, setRebasing] = useState(false); + const [merging, setMerging] = useState(false); + + // Check for conflicts + const hasConflicts = useMemo( + () => Boolean((branchStatus?.conflicted_files?.length ?? 0) > 0), + [branchStatus?.conflicted_files] + ); + + // Merge status information + const mergeInfo = useMemo(() => { + if (!branchStatus?.merges) + return { + hasOpenPR: false, + openPR: null, + hasMergedPR: false, + mergedPR: null, + hasMerged: false, + }; + + const openPR = branchStatus.merges.find( + (m) => m.type === 'pr' && m.pr_info.status === 'open' + ); + + const mergedPR = branchStatus.merges.find( + (m) => m.type === 'pr' && m.pr_info.status === 'merged' + ); + + const merges = branchStatus.merges.filter( + (m) => + m.type === 'direct' || + (m.type === 'pr' && m.pr_info.status === 'merged') + ); + + return { + hasOpenPR: !!openPR, + openPR, + hasMergedPR: !!mergedPR, + mergedPR, + hasMerged: merges.length > 0, + }; + }, [branchStatus?.merges]); + const handleCreatePR = () => { if (selectedAttempt) { NiceModal.show('create-pr', { @@ -54,6 +109,28 @@ export function AttemptHeaderCard({ } }; + const handleRebaseClick = async () => { + setRebasing(true); + try { + await rebaseMutation.mutateAsync(undefined); + } catch (error) { + // Error handling is done by the mutation + } finally { + setRebasing(false); + } + }; + + const handleMergeClick = async () => { + setMerging(true); + try { + await mergeMutation.mutateAsync(); + } catch (error) { + // Error handling is done by the mutation + } finally { + setMerging(false); + } + }; + return (
@@ -109,24 +186,38 @@ export function AttemptHeaderCard({ > {runningDevServer ? 'Stop dev server' : 'Start dev server'} - rebaseMutation.mutate(undefined)} - disabled={!selectedAttempt} - > - Rebase - + {selectedAttempt && + branchStatus && + !mergeInfo.hasMergedPR && + (branchStatus.commits_behind ?? 0) > 0 && ( + + {rebasing ? 'Rebasing...' : 'Rebase'} + + )} Create PR - mergeMutation.mutate()} - disabled={!selectedAttempt} - > - Merge - + {selectedAttempt && branchStatus && !mergeInfo.hasMergedPR && ( + 0) || + isAttemptRunning || + (branchStatus.commits_ahead ?? 0) === 0 + } + > + {merging ? 'Merging...' : 'Merge'} + + )} {/*