Files
vibe-kanban/frontend/src/components/ui-new/actions/useActionVisibility.ts

182 lines
5.5 KiB
TypeScript
Raw Normal View History

2026-01-08 22:14:38 +00:00
import { useMemo } from 'react';
import { useUiPreferencesStore } from '@/stores/useUiPreferencesStore';
import { useDiffViewStore, useDiffViewMode } from '@/stores/useDiffViewStore';
2026-01-08 22:14:38 +00:00
import { useWorkspaceContext } from '@/contexts/WorkspaceContext';
Fix CommandBar input and navigation issues with state machine refactor (Vibe Kanban) (#1885) * Done! I've removed all 7 `shortcut` properties from the action definitions in `index.ts`: - `NewWorkspace`: removed `shortcut: 'N'` - `Settings`: removed `shortcut: ','` - `ToggleSidebar`: removed `shortcut: '['` - `ToggleGitPanel`: removed `shortcut: ']'` - `ToggleChangesMode`: removed `shortcut: 'C'` - `ToggleLogsMode`: removed `shortcut: 'L'` - `TogglePreviewMode`: removed `shortcut: 'P'` The `ActionDefinition` type (and its base `ActionBase` interface) were left unchanged as requested. * fix tailwind nto auto creating styles * I've successfully completed all the changes. Here's a summary of what was done: ## Summary of Changes ### 1. `ActionExecutorContext` (index.ts) - Made all properties non-optional - Removed `gitRepoId` from context (now passed as action parameter) - Properties now use `| null` for values that may not exist ### 2. New `GitActionDefinition` Type (index.ts) - Added new action type with `requiresTarget: 'git'` - Git actions now explicitly require `repoId` as a parameter ### 3. Git Actions Updated (index.ts) - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` function takes `repoId` as third parameter - Simplified implementations (no more fallback repo fetching logic) ### 4. `ActionsContext` Provider (ActionsContext.tsx) - Now calls `useDevServer()` to get dev server state - Provides all required context properties - `executeAction` handles three action types: global, workspace, and git - Removed `contextOverride` pattern ### 5. `ContextBarContainer` (ContextBarContainer.tsx) - Simplified significantly - no longer builds extended context - Uses executor context directly from provider - Removed `containerPath` prop ### 6. `ActionVisibilityContext` (index.ts + useActionVisibility.ts) - Made `editorType`, `devServerState`, `runningDevServerId` required - Visibility hook now provides all values from hooks ### 7. Updated Call Sites - `WorkspacesLayout`: Git actions now pass `repoId` as third parameter - `WorkspacesMain`: Removed `containerPath` prop from ContextBarContainer * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * All changes are complete and type checks pass. Here's a summary of the changes made: ## Summary of Changes ### 1. `ActionExecutorContext` - Made all properties non-optional - Removed `?` from all properties - Removed `gitRepoId` (moved to per-action parameter) - Changed nullable properties to use `| null` type ### 2. New `GitActionDefinition` type - Added new action type with `requiresTarget: 'git'` - Takes `repoId` as explicit third parameter ### 3. Git actions updated - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` receives `repoId` as parameter ### 4. `ActionsContext` provider centralized - Now calls `useDevServer()` to provide dev server state - Provides all required context properties from hooks - `executeAction` handles three action types: global, workspace, git - Removed `contextOverride` pattern ### 5. `ActionVisibilityContext` - Made dev server properties required - `editorType`, `devServerState`, `runningDevServerId` now required - `useActionVisibilityContext` hook provides all values ### 6. `ContextBarContainer` simplified - Removed extended context creation - Uses executor context directly from provider ### 7. Provider order fix - Moved `ExecutionProcessesProvider` up to `NewDesignScope` - Required because `useDevServer` depends on `ExecutionProcessesContext` ### Files Modified: - `vibe-kanban/frontend/src/components/ui-new/actions/index.ts` - `vibe-kanban/frontend/src/components/ui-new/actions/useActionVisibility.ts` - `vibe-kanban/frontend/src/contexts/ActionsContext.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/ContextBarContainer.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx` - `vibe-kanban/frontend/src/components/ui-new/views/WorkspacesMain.tsx` - `vibe-kanban/frontend/src/components/ui-new/scope/NewDesignScope.tsx` * The implementation is complete. Let me summarize what was changed: ## Summary of Changes ### `pages.ts` - Added `'selectRepo'` to `PageId` type for dynamic repo selection page - Created `StaticPageId` type (excludes `selectRepo`) for static page records - Added `RepoItem` interface for repo selection items - Extended `ResolvedGroupItem` union type to include `{ type: 'repo'; repo: RepoItem }` - Updated `Pages` record and `getPageActions` to use `StaticPageId` ### `CommandBarDialog.tsx` - Added `repos` from `useWorkspaceContext()` - Added `pendingGitAction` state to track git actions awaiting repo selection - Reset `pendingGitAction` when dialog opens/closes - Modified `getPageWithItems` to generate dynamic `selectRepo` page with repo list - Updated `handleSelect` to: - Execute git actions immediately with single repo - Navigate to `selectRepo` page when multiple repos exist - Execute pending git action when user selects a repo - Clear `pendingGitAction` when navigating back from `selectRepo` page ### `CommandBar.tsx` - Added `FolderIcon` import - Added rendering for `'repo'` item type with folder icon ## Behavior 1. **Single repo**: When user selects a git action (e.g., "Create Pull Request") and the workspace has only one git repo, the action executes immediately with that repo's ID. 2. **Multiple repos**: When workspace has multiple repos, selecting a git action navigates to a "Select Repository" page where the user can choose which repo to use, then the action executes with the selected repo. 3. **Back navigation**: Pressing back from the repo selection page clears the pending action and returns to the previous page. * Done! The `CommandBarDialog` has been refactored to use a state machine pattern with `useReducer`. Here's a summary of the changes: ## Summary of Changes **State Machine Types (lines 38-67):** - `CommandBarState`: Discriminated union with `browsing` and `selectingRepo` states - `CommandBarEvent`: `RESET`, `SEARCH_CHANGE`, `GO_BACK`, `SELECT_ITEM` - `CommandBarEffect`: Side effects returned from reducer (`none` or `execute`) **Reducer (lines 73-201):** - Pure function returning `[newState, effect]` tuple - Handles all state transitions based on current state and event - Git action flow: single repo → execute, multiple repos → transition to `selectingRepo` **Component Changes:** - Replaced 4 `useState` calls with single `useReducer` - `currentPage` is now derived from state (line 249-250) - `handleSelect` computes effect from reducer, dispatches event, then handles effect (lines 456-475) - `goBack` and `handleSearchChange` simply dispatch events (lines 446-453) **Benefits:** - All state transitions in one place (the reducer) - State transitions are pure and testable - Clear separation between state updates and side effects - Impossible states are unrepresentable (e.g., can't have `pendingAction` while `browsing`) * Done! Here's the final result: ## Summary **Before:** 520 lines in 1 file (after initial state machine refactor) **After:** 487 lines across 4 files | File | Lines | Purpose | |------|-------|---------| | `CommandBarDialog.tsx` | 101 | Thin orchestration (down from 520) | | `useCommandBarState.ts` | 177 | State machine hook | | `useResolvedPage.ts` | 150 | Page building logic | | `injectSearchMatches.ts` | 59 | DRY search helper | **Key wins:** - Main component: **520 → 101 lines** (80% reduction) - Search injection code: **100 → 59 lines** (4× duplication eliminated) - Each file has single responsibility - State machine is testable in isolation - Page building logic is reusable * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * Done! Here are the final results: ## Final Summary | File | Before | After | Reduction | |------|--------|-------|-----------| | `CommandBarDialog.tsx` | 101 | 106 | +5 (linter formatting) | | `useCommandBarState.ts` | 177 | 95 | **-82 (46%)** | | `useResolvedPage.ts` | 150 | 95 | **-55 (37%)** | | `injectSearchMatches.ts` | 59 | 42 | **-17 (29%)** | | **Total** | **487** | **338** | **-149 (31%)** | **Original single file:** 398 lines **Final 4 files:** 338 lines (15% less code overall) Key wins: - Main component is now just 106 lines of thin orchestration - State machine is flattened (if statements instead of nested switch/case) - Page resolution is simplified with reused types - Search injection uses reduce() instead of for-loop - All files pass type check * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * I've fixed both issues with the CommandBar. Here's a summary of what was wrong and how I fixed it: ## Root Causes ### Issue 1: Nested `<Command>` Components The `CommandDialog` component was wrapping children in a `<Command>` component, and `CommandBar` also rendered its own `<Command>`. This nested cmdk context caused conflicts with state management. **Fix:** Removed the `<Command>` wrapper from `CommandDialog` in `Command.tsx:26-44`, and moved the styling classes to `CommandBar`'s `<Command>` component. ### Issue 2: Unstable `dispatch` Function Causing Infinite RESET Loop In `useCommandBarState.ts`, the `dispatch` function had `[state, repoCount]` in its `useCallback` dependency array. This caused `dispatch` to be recreated on every state change. In `CommandBarDialog.tsx`, the `useEffect` that resets state when the modal opens had `dispatch` in its dependency array: ```tsx useEffect(() => { if (modal.visible) { dispatch({ type: 'RESET', page }); } }, [modal.visible, page, dispatch]); // dispatch changes → useEffect runs → RESET ``` This created an infinite loop: 1. User types → state updates → `dispatch` recreated 2. `dispatch` changed → `useEffect` runs → `RESET` dispatched 3. State resets to empty → repeat **Fix:** Made `dispatch` stable by using refs instead of state in dependencies: ```tsx const stateRef = useRef<CommandBarState>(browsing(initialPage)); const repoCountRef = useRef(repoCount); const dispatch = useCallback( (event: CommandBarEvent): CommandBarEffect => { const [, effect] = reducer(stateRef.current, event, repoCountRef.current); rawDispatch(event); return effect; }, [] // No dependencies - uses refs for current values ); ``` ## Files Changed 1. `vibe-kanban/frontend/src/components/ui-new/primitives/Command.tsx` - Removed nested `<Command>` wrapper from `CommandDialog` 2. `vibe-kanban/frontend/src/components/ui-new/primitives/CommandBar.tsx` - Added styling classes that were on the removed wrapper 3. `vibe-kanban/frontend/src/components/ui-new/dialogs/commandBar/useCommandBarState.ts` - Made `dispatch` stable using refs
2026-01-09 16:25:42 +00:00
import { useUserSystem } from '@/components/ConfigProvider';
import { useDevServer } from '@/hooks/useDevServer';
Add Push button to Git panel when PR is open with unpushed commits (Vibe Kanban) (#1951) * The implementation is complete. Here's a summary of what I changed: ## Summary Added a "Push" button to the workspaces UI Git panel. The changes allow users to push changes when a PR is already open. ### Files Modified: 1. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`** (lines 27-42) - Added `'push'` to the `RepoAction` type union - Added a new option to `repoActionOptions` array with the Push button configuration using `ArrowUpIcon` 2. **`frontend/src/components/ui-new/actions/index.ts`** (lines 33, 691-707) - Imported `ArrowUpIcon` from phosphor-icons - Added `GitPush` action definition that: - Calls `attemptsApi.push()` with the workspace and repo IDs - Handles the `force_push_required` error case - Invalidates workspace queries on success to refresh the UI 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`** (line 103) - Added mapping from `'push'` action to `Actions.GitPush` in the action handler The Push button now appears in the Git panel dropdown alongside "Open pull request" and "Merge" options. * The type check passes. Here's a summary of the updated changes: ## Summary The Push button now appears **next to the PR number** when: 1. A PR is already open (`prStatus === 'open'`) 2. There are new commits ahead (`commitsAhead > 0`) ### Changes Made: **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Removed `push` from the `repoActionOptions` dropdown (lines 34-41) - Added a dedicated Push button in the PR status row (lines 213-222) that: - Only appears when `prStatus === 'open'` AND `commitsAhead > 0` - Uses brand primary styling (`bg-brand-primary`) to make it prominent - Displays with an up arrow icon and "Push" label using existing translation - Calls `onActionsClick?.('push')` when clicked The `GitPush` action and handler mapping from the previous changes remain in place to handle the push operation. * Done. The Push button now uses the same styling as the "Open PR" button: `bg-panel text-normal hover:bg-tertiary`. * The type check passes. Here's a summary of the fix: ## Summary The Push button now correctly uses `remoteCommitsAhead` (unpushed commits to remote) instead of `commitsAhead` (total commits ahead of target branch) to determine when to show. ### Changes Made: 1. **`frontend/src/components/ui-new/views/GitPanel.tsx`**: - Added `remoteCommitsAhead: number` to the `RepoInfo` interface - Pass `remoteCommitsAhead` to `RepoCard` 2. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Added `remoteCommitsAhead?: number` to `RepoCardProps` - Changed the push button condition from `commitsAhead > 0` to `remoteCommitsAhead > 0` 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`**: - Added `remoteCommitsAhead: repoStatus?.remote_commits_ahead ?? 0` to the `repoInfos` mapping Now the Push button will: - Only appear when there's an open PR AND there are unpushed commits (`remote_commits_ahead > 0`) - Disappear after a successful push (when `remote_commits_ahead` becomes 0) - Reappear when new commits are made locally that haven't been pushed * The type check passes. Here's a summary of the changes: ## Summary The Push button now hides immediately when clicked and only reappears when new commits are available: ### Changes to `RepoCard.tsx`: - Added `useState` and `useRef` imports - Added `isPushHidden` state to track when the button should be hidden - Added `lastRemoteCommitsAhead` ref to track the previous value - Added `useEffect` that resets `isPushHidden` to `false` when `remoteCommitsAhead` increases (meaning new commits are available) - Added `handlePushClick` function that hides the button immediately and triggers the push action - Updated the button condition to include `!isPushHidden` The button will: 1. Hide immediately when clicked (no double-clicking possible) 2. Stay hidden until `remoteCommitsAhead` increases above the last known value 3. Reappear when new local commits are made that need to be pushed * Both the linter and type check pass. The linter errors are fixed. Here's a summary of the changes: ## Summary of Linter Fixes The linter was flagging that `RepoCard` (a presentational component) should not manage state or have side effects. I refactored the code to lift the state up to the container component: ### Changes: 1. **`RepoCard.tsx`** - Made purely presentational: - Removed `useState`, `useRef`, `useEffect` imports - Removed `remoteCommitsAhead` prop - Added `showPushButton` prop (boolean, controlled by parent) - Added `onPushClick` callback prop - The button now just renders based on `showPushButton` and calls `onPushClick` 2. **`GitPanel.tsx`** - Updated interface and props: - `RepoInfo` now has optional `remoteCommitsAhead` and `showPushButton` fields - Added `onPushClick` callback to `GitPanelProps` - Passes `showPushButton` and `onPushClick` to `RepoCard` 3. **`WorkspacesLayout.tsx`** - State management moved here: - Added `hiddenPushRepos` state (Set of repo IDs where push was clicked) - Added `lastRemoteCommitsAhead` ref to track previous values - Added `useEffect` to reset hidden state when `remoteCommitsAhead` increases - Added `repoInfosWithPushButton` memo that computes `showPushButton` for each repo - Added `handlePushClick` that hides button immediately and executes push action * prettier fix * Done. I've added `GitPush` to the Git actions list in the command bar. It now appears between "Merge" and "Rebase" in the Git Actions menu. * Both type check and lint pass. Done! The Push action in the command bar now has the same visibility conditions as in the Git panel - it only appears when: 1. There's a workspace 2. There are git repos 3. There's an open PR (`hasOpenPR`) 4. There are unpushed commits (`hasUnpushedCommits`) ### Summary of changes: 1. **`frontend/src/components/ui-new/actions/index.ts`**: - Added `hasOpenPR` and `hasUnpushedCommits` to `ActionVisibilityContext` - Updated `GitPush.isVisible` to check these new context properties 2. **`frontend/src/components/ui-new/actions/useActionVisibility.ts`**: - Added `useBranchStatus` hook to get branch status data - Computed `hasOpenPR` by checking if any repo has an open PR in merges - Computed `hasUnpushedCommits` by checking if any repo has `remote_commits_ahead > 0` - Added these values to the returned context * The refactoring is complete. Here's a summary of the changes: ## Summary of Changes The push button logic has been simplified by using React Query's mutation state instead of manually tracking hidden state with refs and effects. ### Key Changes: 1. **WorkspacesLayout.tsx (`GitPanelContainer`)**: - Removed the complex `useEffect` that tracked `lastRemoteCommitsAhead` and managed `hiddenPushRepos` state - Removed `hiddenPushRepos` state and `lastRemoteCommitsAhead` ref - Added `usePush` hook with callbacks that: - On success: awaits query invalidation before clearing the loading state - On error: clears the loading state - Added simple `pushingRepoId` state to track which repo is being pushed - The button now shows loading state during push and automatically disappears after refetch if `remoteCommitsAhead` becomes 0 2. **GitPanel.tsx**: - Added `isPushPending?: boolean` to `RepoInfo` interface - Passes `isPushPending` to `RepoCard` 3. **RepoCard.tsx**: - Added `isPushPending` prop - Button shows while either `showPushButton` or `isPushPending` is true - Button is disabled and shows spinner with "Pushing..." text when pending - Uses existing translation key `git.states.pushing` ### How it works now: 1. User clicks push button 2. Button immediately shows loading state (spinner + "Pushing...") 3. Button is disabled to prevent multiple clicks 4. Push mutation runs 5. On success, query invalidation completes before clearing loading state 6. Once `branchStatus` refetches, if `remoteCommitsAhead` is 0, `showPushButton` becomes false and button disappears 7. On error, loading state clears and button returns to normal This approach is simpler and more predictable than manually tracking previous values. * Done. The push button now shows success feedback like the old UI: ## Summary of Changes The push button now has three states: 1. **Normal**: Shows "Push" with an up arrow icon 2. **Pending**: Shows "Pushing..." with a spinning icon, button is disabled 3. **Success**: Shows "Pushed!" with a green checkmark icon and green background for 2 seconds The flow is: 1. User clicks "Push" 2. Button immediately shows "Pushing..." with spinner 3. Push completes, queries are invalidated and refetched 4. Button shows green "Pushed!" for 2 seconds 5. Button disappears (if no more commits to push) or returns to normal state * All code review issues have been addressed: ## Summary of Changes 1. **Unified push logic** - Now uses `executeAction(Actions.GitPush, ...)` instead of calling `usePush` hook directly. This ensures consistent error handling between the button and command bar. 2. **Proper setTimeout cleanup** - Added `successTimeoutRef` with a cleanup effect that clears the timeout when the component unmounts: ```typescript const successTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); useEffect(() => { return () => { if (successTimeoutRef.current) { clearTimeout(successTimeoutRef.current); } }; }, []); ``` 3. **Error feedback** - Added error state handling: - New `isPushError` prop flows through GitPanel to RepoCard - Button shows red "Failed" with warning icon when push fails - Error state clears after 3 seconds - Added `git.states.pushFailed` translation key 4. **Simplified state management** - Changed from separate `pushingRepoId` and `pushSuccessRepoId` to a single `pushStates` record with `PushState` type (`'idle' | 'pending' | 'success' | 'error'`) * All i18n checks pass now. * Both code review issues have been addressed: ## Summary of Changes ### 1. Fixed stale closure issue - Added a `pushStatesRef` that mirrors the `pushStates` state - Changed the check from `pushStates[repoId]` to `pushStatesRef.current[repoId]` - Removed `pushStates` from the dependency array ### 2. Added proper error logging and user-facing error message - Modified `ActionsContext.tsx` to re-throw errors after showing the dialog, so callers can handle them - Added `console.error('Push failed:', error)` for debugging purposes - The error dialog is already shown by `executeAction` via `ConfirmDialog` - The button now properly shows the error state when push fails * fmt * The fix is in place. The key change is: - When we're in a "push flow" (state is `pending`, `success`, or `error`), we set `showPushButton: false` but the button remains visible because of the `isPushPending || isPushSuccess || isPushError` condition in RepoCard - This prevents the race condition where the query refetch could hide the button before we set the success state * All checks pass. The changes now: 1. **Reverted ActionsContext** - Removed the re-throw to maintain backwards compatibility 2. **Use `usePush` hook directly** - For proper success/error detection instead of going through `executeAction` 3. **Show error dialog manually** - Using `ConfirmDialog.show()` when push fails 4. **Track current repo with ref** - `currentPushRepoRef` tracks which repo is being pushed so callbacks can update the correct state * All checks pass. Here's a summary of the force push handling: ## Force Push Functionality Yes, force push is fully supported. Here's how it works: **Old UI (`useGitOperations.ts`):** 1. When `usePush` fails with `errorData.type === 'force_push_required'` 2. It automatically shows `ForcePushDialog` 3. The dialog warns the user about the consequences and lets them confirm 4. If confirmed, it calls `useForcePush` to execute the force push **New UI (now updated):** 1. When the push button is clicked and fails with `force_push_required` 2. The push state is reset to `idle` (so the button returns to normal) 3. `ForcePushDialog` is shown with the attemptId and repoId 4. User can confirm or cancel the force push from the dialog The `ForcePushDialog` handles its own loading state, success/error feedback, and API call internally via `useForcePush` hook. * clear pushStates on workspace change Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 15:13:57 +01:00
import { useBranchStatus } from '@/hooks/useBranchStatus';
import { useExecutionProcessesContext } from '@/contexts/ExecutionProcessesContext';
Add Push button to Git panel when PR is open with unpushed commits (Vibe Kanban) (#1951) * The implementation is complete. Here's a summary of what I changed: ## Summary Added a "Push" button to the workspaces UI Git panel. The changes allow users to push changes when a PR is already open. ### Files Modified: 1. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`** (lines 27-42) - Added `'push'` to the `RepoAction` type union - Added a new option to `repoActionOptions` array with the Push button configuration using `ArrowUpIcon` 2. **`frontend/src/components/ui-new/actions/index.ts`** (lines 33, 691-707) - Imported `ArrowUpIcon` from phosphor-icons - Added `GitPush` action definition that: - Calls `attemptsApi.push()` with the workspace and repo IDs - Handles the `force_push_required` error case - Invalidates workspace queries on success to refresh the UI 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`** (line 103) - Added mapping from `'push'` action to `Actions.GitPush` in the action handler The Push button now appears in the Git panel dropdown alongside "Open pull request" and "Merge" options. * The type check passes. Here's a summary of the updated changes: ## Summary The Push button now appears **next to the PR number** when: 1. A PR is already open (`prStatus === 'open'`) 2. There are new commits ahead (`commitsAhead > 0`) ### Changes Made: **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Removed `push` from the `repoActionOptions` dropdown (lines 34-41) - Added a dedicated Push button in the PR status row (lines 213-222) that: - Only appears when `prStatus === 'open'` AND `commitsAhead > 0` - Uses brand primary styling (`bg-brand-primary`) to make it prominent - Displays with an up arrow icon and "Push" label using existing translation - Calls `onActionsClick?.('push')` when clicked The `GitPush` action and handler mapping from the previous changes remain in place to handle the push operation. * Done. The Push button now uses the same styling as the "Open PR" button: `bg-panel text-normal hover:bg-tertiary`. * The type check passes. Here's a summary of the fix: ## Summary The Push button now correctly uses `remoteCommitsAhead` (unpushed commits to remote) instead of `commitsAhead` (total commits ahead of target branch) to determine when to show. ### Changes Made: 1. **`frontend/src/components/ui-new/views/GitPanel.tsx`**: - Added `remoteCommitsAhead: number` to the `RepoInfo` interface - Pass `remoteCommitsAhead` to `RepoCard` 2. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Added `remoteCommitsAhead?: number` to `RepoCardProps` - Changed the push button condition from `commitsAhead > 0` to `remoteCommitsAhead > 0` 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`**: - Added `remoteCommitsAhead: repoStatus?.remote_commits_ahead ?? 0` to the `repoInfos` mapping Now the Push button will: - Only appear when there's an open PR AND there are unpushed commits (`remote_commits_ahead > 0`) - Disappear after a successful push (when `remote_commits_ahead` becomes 0) - Reappear when new commits are made locally that haven't been pushed * The type check passes. Here's a summary of the changes: ## Summary The Push button now hides immediately when clicked and only reappears when new commits are available: ### Changes to `RepoCard.tsx`: - Added `useState` and `useRef` imports - Added `isPushHidden` state to track when the button should be hidden - Added `lastRemoteCommitsAhead` ref to track the previous value - Added `useEffect` that resets `isPushHidden` to `false` when `remoteCommitsAhead` increases (meaning new commits are available) - Added `handlePushClick` function that hides the button immediately and triggers the push action - Updated the button condition to include `!isPushHidden` The button will: 1. Hide immediately when clicked (no double-clicking possible) 2. Stay hidden until `remoteCommitsAhead` increases above the last known value 3. Reappear when new local commits are made that need to be pushed * Both the linter and type check pass. The linter errors are fixed. Here's a summary of the changes: ## Summary of Linter Fixes The linter was flagging that `RepoCard` (a presentational component) should not manage state or have side effects. I refactored the code to lift the state up to the container component: ### Changes: 1. **`RepoCard.tsx`** - Made purely presentational: - Removed `useState`, `useRef`, `useEffect` imports - Removed `remoteCommitsAhead` prop - Added `showPushButton` prop (boolean, controlled by parent) - Added `onPushClick` callback prop - The button now just renders based on `showPushButton` and calls `onPushClick` 2. **`GitPanel.tsx`** - Updated interface and props: - `RepoInfo` now has optional `remoteCommitsAhead` and `showPushButton` fields - Added `onPushClick` callback to `GitPanelProps` - Passes `showPushButton` and `onPushClick` to `RepoCard` 3. **`WorkspacesLayout.tsx`** - State management moved here: - Added `hiddenPushRepos` state (Set of repo IDs where push was clicked) - Added `lastRemoteCommitsAhead` ref to track previous values - Added `useEffect` to reset hidden state when `remoteCommitsAhead` increases - Added `repoInfosWithPushButton` memo that computes `showPushButton` for each repo - Added `handlePushClick` that hides button immediately and executes push action * prettier fix * Done. I've added `GitPush` to the Git actions list in the command bar. It now appears between "Merge" and "Rebase" in the Git Actions menu. * Both type check and lint pass. Done! The Push action in the command bar now has the same visibility conditions as in the Git panel - it only appears when: 1. There's a workspace 2. There are git repos 3. There's an open PR (`hasOpenPR`) 4. There are unpushed commits (`hasUnpushedCommits`) ### Summary of changes: 1. **`frontend/src/components/ui-new/actions/index.ts`**: - Added `hasOpenPR` and `hasUnpushedCommits` to `ActionVisibilityContext` - Updated `GitPush.isVisible` to check these new context properties 2. **`frontend/src/components/ui-new/actions/useActionVisibility.ts`**: - Added `useBranchStatus` hook to get branch status data - Computed `hasOpenPR` by checking if any repo has an open PR in merges - Computed `hasUnpushedCommits` by checking if any repo has `remote_commits_ahead > 0` - Added these values to the returned context * The refactoring is complete. Here's a summary of the changes: ## Summary of Changes The push button logic has been simplified by using React Query's mutation state instead of manually tracking hidden state with refs and effects. ### Key Changes: 1. **WorkspacesLayout.tsx (`GitPanelContainer`)**: - Removed the complex `useEffect` that tracked `lastRemoteCommitsAhead` and managed `hiddenPushRepos` state - Removed `hiddenPushRepos` state and `lastRemoteCommitsAhead` ref - Added `usePush` hook with callbacks that: - On success: awaits query invalidation before clearing the loading state - On error: clears the loading state - Added simple `pushingRepoId` state to track which repo is being pushed - The button now shows loading state during push and automatically disappears after refetch if `remoteCommitsAhead` becomes 0 2. **GitPanel.tsx**: - Added `isPushPending?: boolean` to `RepoInfo` interface - Passes `isPushPending` to `RepoCard` 3. **RepoCard.tsx**: - Added `isPushPending` prop - Button shows while either `showPushButton` or `isPushPending` is true - Button is disabled and shows spinner with "Pushing..." text when pending - Uses existing translation key `git.states.pushing` ### How it works now: 1. User clicks push button 2. Button immediately shows loading state (spinner + "Pushing...") 3. Button is disabled to prevent multiple clicks 4. Push mutation runs 5. On success, query invalidation completes before clearing loading state 6. Once `branchStatus` refetches, if `remoteCommitsAhead` is 0, `showPushButton` becomes false and button disappears 7. On error, loading state clears and button returns to normal This approach is simpler and more predictable than manually tracking previous values. * Done. The push button now shows success feedback like the old UI: ## Summary of Changes The push button now has three states: 1. **Normal**: Shows "Push" with an up arrow icon 2. **Pending**: Shows "Pushing..." with a spinning icon, button is disabled 3. **Success**: Shows "Pushed!" with a green checkmark icon and green background for 2 seconds The flow is: 1. User clicks "Push" 2. Button immediately shows "Pushing..." with spinner 3. Push completes, queries are invalidated and refetched 4. Button shows green "Pushed!" for 2 seconds 5. Button disappears (if no more commits to push) or returns to normal state * All code review issues have been addressed: ## Summary of Changes 1. **Unified push logic** - Now uses `executeAction(Actions.GitPush, ...)` instead of calling `usePush` hook directly. This ensures consistent error handling between the button and command bar. 2. **Proper setTimeout cleanup** - Added `successTimeoutRef` with a cleanup effect that clears the timeout when the component unmounts: ```typescript const successTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); useEffect(() => { return () => { if (successTimeoutRef.current) { clearTimeout(successTimeoutRef.current); } }; }, []); ``` 3. **Error feedback** - Added error state handling: - New `isPushError` prop flows through GitPanel to RepoCard - Button shows red "Failed" with warning icon when push fails - Error state clears after 3 seconds - Added `git.states.pushFailed` translation key 4. **Simplified state management** - Changed from separate `pushingRepoId` and `pushSuccessRepoId` to a single `pushStates` record with `PushState` type (`'idle' | 'pending' | 'success' | 'error'`) * All i18n checks pass now. * Both code review issues have been addressed: ## Summary of Changes ### 1. Fixed stale closure issue - Added a `pushStatesRef` that mirrors the `pushStates` state - Changed the check from `pushStates[repoId]` to `pushStatesRef.current[repoId]` - Removed `pushStates` from the dependency array ### 2. Added proper error logging and user-facing error message - Modified `ActionsContext.tsx` to re-throw errors after showing the dialog, so callers can handle them - Added `console.error('Push failed:', error)` for debugging purposes - The error dialog is already shown by `executeAction` via `ConfirmDialog` - The button now properly shows the error state when push fails * fmt * The fix is in place. The key change is: - When we're in a "push flow" (state is `pending`, `success`, or `error`), we set `showPushButton: false` but the button remains visible because of the `isPushPending || isPushSuccess || isPushError` condition in RepoCard - This prevents the race condition where the query refetch could hide the button before we set the success state * All checks pass. The changes now: 1. **Reverted ActionsContext** - Removed the re-throw to maintain backwards compatibility 2. **Use `usePush` hook directly** - For proper success/error detection instead of going through `executeAction` 3. **Show error dialog manually** - Using `ConfirmDialog.show()` when push fails 4. **Track current repo with ref** - `currentPushRepoRef` tracks which repo is being pushed so callbacks can update the correct state * All checks pass. Here's a summary of the force push handling: ## Force Push Functionality Yes, force push is fully supported. Here's how it works: **Old UI (`useGitOperations.ts`):** 1. When `usePush` fails with `errorData.type === 'force_push_required'` 2. It automatically shows `ForcePushDialog` 3. The dialog warns the user about the consequences and lets them confirm 4. If confirmed, it calls `useForcePush` to execute the force push **New UI (now updated):** 1. When the push button is clicked and fails with `force_push_required` 2. The push state is reset to `idle` (so the button returns to normal) 3. `ForcePushDialog` is shown with the attemptId and repoId 4. User can confirm or cancel the force push from the dialog The `ForcePushDialog` handles its own loading state, success/error feedback, and API call internally via `useForcePush` hook. * clear pushStates on workspace change Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 15:13:57 +01:00
import type { Workspace, Merge } from 'shared/types';
2026-01-08 22:14:38 +00:00
import type {
ActionVisibilityContext,
ActionDefinition,
ActionIcon,
Fix CommandBar input and navigation issues with state machine refactor (Vibe Kanban) (#1885) * Done! I've removed all 7 `shortcut` properties from the action definitions in `index.ts`: - `NewWorkspace`: removed `shortcut: 'N'` - `Settings`: removed `shortcut: ','` - `ToggleSidebar`: removed `shortcut: '['` - `ToggleGitPanel`: removed `shortcut: ']'` - `ToggleChangesMode`: removed `shortcut: 'C'` - `ToggleLogsMode`: removed `shortcut: 'L'` - `TogglePreviewMode`: removed `shortcut: 'P'` The `ActionDefinition` type (and its base `ActionBase` interface) were left unchanged as requested. * fix tailwind nto auto creating styles * I've successfully completed all the changes. Here's a summary of what was done: ## Summary of Changes ### 1. `ActionExecutorContext` (index.ts) - Made all properties non-optional - Removed `gitRepoId` from context (now passed as action parameter) - Properties now use `| null` for values that may not exist ### 2. New `GitActionDefinition` Type (index.ts) - Added new action type with `requiresTarget: 'git'` - Git actions now explicitly require `repoId` as a parameter ### 3. Git Actions Updated (index.ts) - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` function takes `repoId` as third parameter - Simplified implementations (no more fallback repo fetching logic) ### 4. `ActionsContext` Provider (ActionsContext.tsx) - Now calls `useDevServer()` to get dev server state - Provides all required context properties - `executeAction` handles three action types: global, workspace, and git - Removed `contextOverride` pattern ### 5. `ContextBarContainer` (ContextBarContainer.tsx) - Simplified significantly - no longer builds extended context - Uses executor context directly from provider - Removed `containerPath` prop ### 6. `ActionVisibilityContext` (index.ts + useActionVisibility.ts) - Made `editorType`, `devServerState`, `runningDevServerId` required - Visibility hook now provides all values from hooks ### 7. Updated Call Sites - `WorkspacesLayout`: Git actions now pass `repoId` as third parameter - `WorkspacesMain`: Removed `containerPath` prop from ContextBarContainer * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * All changes are complete and type checks pass. Here's a summary of the changes made: ## Summary of Changes ### 1. `ActionExecutorContext` - Made all properties non-optional - Removed `?` from all properties - Removed `gitRepoId` (moved to per-action parameter) - Changed nullable properties to use `| null` type ### 2. New `GitActionDefinition` type - Added new action type with `requiresTarget: 'git'` - Takes `repoId` as explicit third parameter ### 3. Git actions updated - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` receives `repoId` as parameter ### 4. `ActionsContext` provider centralized - Now calls `useDevServer()` to provide dev server state - Provides all required context properties from hooks - `executeAction` handles three action types: global, workspace, git - Removed `contextOverride` pattern ### 5. `ActionVisibilityContext` - Made dev server properties required - `editorType`, `devServerState`, `runningDevServerId` now required - `useActionVisibilityContext` hook provides all values ### 6. `ContextBarContainer` simplified - Removed extended context creation - Uses executor context directly from provider ### 7. Provider order fix - Moved `ExecutionProcessesProvider` up to `NewDesignScope` - Required because `useDevServer` depends on `ExecutionProcessesContext` ### Files Modified: - `vibe-kanban/frontend/src/components/ui-new/actions/index.ts` - `vibe-kanban/frontend/src/components/ui-new/actions/useActionVisibility.ts` - `vibe-kanban/frontend/src/contexts/ActionsContext.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/ContextBarContainer.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx` - `vibe-kanban/frontend/src/components/ui-new/views/WorkspacesMain.tsx` - `vibe-kanban/frontend/src/components/ui-new/scope/NewDesignScope.tsx` * The implementation is complete. Let me summarize what was changed: ## Summary of Changes ### `pages.ts` - Added `'selectRepo'` to `PageId` type for dynamic repo selection page - Created `StaticPageId` type (excludes `selectRepo`) for static page records - Added `RepoItem` interface for repo selection items - Extended `ResolvedGroupItem` union type to include `{ type: 'repo'; repo: RepoItem }` - Updated `Pages` record and `getPageActions` to use `StaticPageId` ### `CommandBarDialog.tsx` - Added `repos` from `useWorkspaceContext()` - Added `pendingGitAction` state to track git actions awaiting repo selection - Reset `pendingGitAction` when dialog opens/closes - Modified `getPageWithItems` to generate dynamic `selectRepo` page with repo list - Updated `handleSelect` to: - Execute git actions immediately with single repo - Navigate to `selectRepo` page when multiple repos exist - Execute pending git action when user selects a repo - Clear `pendingGitAction` when navigating back from `selectRepo` page ### `CommandBar.tsx` - Added `FolderIcon` import - Added rendering for `'repo'` item type with folder icon ## Behavior 1. **Single repo**: When user selects a git action (e.g., "Create Pull Request") and the workspace has only one git repo, the action executes immediately with that repo's ID. 2. **Multiple repos**: When workspace has multiple repos, selecting a git action navigates to a "Select Repository" page where the user can choose which repo to use, then the action executes with the selected repo. 3. **Back navigation**: Pressing back from the repo selection page clears the pending action and returns to the previous page. * Done! The `CommandBarDialog` has been refactored to use a state machine pattern with `useReducer`. Here's a summary of the changes: ## Summary of Changes **State Machine Types (lines 38-67):** - `CommandBarState`: Discriminated union with `browsing` and `selectingRepo` states - `CommandBarEvent`: `RESET`, `SEARCH_CHANGE`, `GO_BACK`, `SELECT_ITEM` - `CommandBarEffect`: Side effects returned from reducer (`none` or `execute`) **Reducer (lines 73-201):** - Pure function returning `[newState, effect]` tuple - Handles all state transitions based on current state and event - Git action flow: single repo → execute, multiple repos → transition to `selectingRepo` **Component Changes:** - Replaced 4 `useState` calls with single `useReducer` - `currentPage` is now derived from state (line 249-250) - `handleSelect` computes effect from reducer, dispatches event, then handles effect (lines 456-475) - `goBack` and `handleSearchChange` simply dispatch events (lines 446-453) **Benefits:** - All state transitions in one place (the reducer) - State transitions are pure and testable - Clear separation between state updates and side effects - Impossible states are unrepresentable (e.g., can't have `pendingAction` while `browsing`) * Done! Here's the final result: ## Summary **Before:** 520 lines in 1 file (after initial state machine refactor) **After:** 487 lines across 4 files | File | Lines | Purpose | |------|-------|---------| | `CommandBarDialog.tsx` | 101 | Thin orchestration (down from 520) | | `useCommandBarState.ts` | 177 | State machine hook | | `useResolvedPage.ts` | 150 | Page building logic | | `injectSearchMatches.ts` | 59 | DRY search helper | **Key wins:** - Main component: **520 → 101 lines** (80% reduction) - Search injection code: **100 → 59 lines** (4× duplication eliminated) - Each file has single responsibility - State machine is testable in isolation - Page building logic is reusable * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * Done! Here are the final results: ## Final Summary | File | Before | After | Reduction | |------|--------|-------|-----------| | `CommandBarDialog.tsx` | 101 | 106 | +5 (linter formatting) | | `useCommandBarState.ts` | 177 | 95 | **-82 (46%)** | | `useResolvedPage.ts` | 150 | 95 | **-55 (37%)** | | `injectSearchMatches.ts` | 59 | 42 | **-17 (29%)** | | **Total** | **487** | **338** | **-149 (31%)** | **Original single file:** 398 lines **Final 4 files:** 338 lines (15% less code overall) Key wins: - Main component is now just 106 lines of thin orchestration - State machine is flattened (if statements instead of nested switch/case) - Page resolution is simplified with reused types - Search injection uses reduce() instead of for-loop - All files pass type check * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * I've fixed both issues with the CommandBar. Here's a summary of what was wrong and how I fixed it: ## Root Causes ### Issue 1: Nested `<Command>` Components The `CommandDialog` component was wrapping children in a `<Command>` component, and `CommandBar` also rendered its own `<Command>`. This nested cmdk context caused conflicts with state management. **Fix:** Removed the `<Command>` wrapper from `CommandDialog` in `Command.tsx:26-44`, and moved the styling classes to `CommandBar`'s `<Command>` component. ### Issue 2: Unstable `dispatch` Function Causing Infinite RESET Loop In `useCommandBarState.ts`, the `dispatch` function had `[state, repoCount]` in its `useCallback` dependency array. This caused `dispatch` to be recreated on every state change. In `CommandBarDialog.tsx`, the `useEffect` that resets state when the modal opens had `dispatch` in its dependency array: ```tsx useEffect(() => { if (modal.visible) { dispatch({ type: 'RESET', page }); } }, [modal.visible, page, dispatch]); // dispatch changes → useEffect runs → RESET ``` This created an infinite loop: 1. User types → state updates → `dispatch` recreated 2. `dispatch` changed → `useEffect` runs → `RESET` dispatched 3. State resets to empty → repeat **Fix:** Made `dispatch` stable by using refs instead of state in dependencies: ```tsx const stateRef = useRef<CommandBarState>(browsing(initialPage)); const repoCountRef = useRef(repoCount); const dispatch = useCallback( (event: CommandBarEvent): CommandBarEffect => { const [, effect] = reducer(stateRef.current, event, repoCountRef.current); rawDispatch(event); return effect; }, [] // No dependencies - uses refs for current values ); ``` ## Files Changed 1. `vibe-kanban/frontend/src/components/ui-new/primitives/Command.tsx` - Removed nested `<Command>` wrapper from `CommandDialog` 2. `vibe-kanban/frontend/src/components/ui-new/primitives/CommandBar.tsx` - Added styling classes that were on the removed wrapper 3. `vibe-kanban/frontend/src/components/ui-new/dialogs/commandBar/useCommandBarState.ts` - Made `dispatch` stable using refs
2026-01-09 16:25:42 +00:00
DevServerState,
2026-01-08 22:14:38 +00:00
} from './index';
import { resolveLabel } from './index';
import type { CommandBarPage } from './pages';
/**
* Hook that builds the visibility context from stores/context.
* Used by both NavbarContainer and CommandBarDialog to evaluate
* action visibility and state conditions.
*/
export function useActionVisibilityContext(): ActionVisibilityContext {
const layout = useUiPreferencesStore();
Fix CommandBar input and navigation issues with state machine refactor (Vibe Kanban) (#1885) * Done! I've removed all 7 `shortcut` properties from the action definitions in `index.ts`: - `NewWorkspace`: removed `shortcut: 'N'` - `Settings`: removed `shortcut: ','` - `ToggleSidebar`: removed `shortcut: '['` - `ToggleGitPanel`: removed `shortcut: ']'` - `ToggleChangesMode`: removed `shortcut: 'C'` - `ToggleLogsMode`: removed `shortcut: 'L'` - `TogglePreviewMode`: removed `shortcut: 'P'` The `ActionDefinition` type (and its base `ActionBase` interface) were left unchanged as requested. * fix tailwind nto auto creating styles * I've successfully completed all the changes. Here's a summary of what was done: ## Summary of Changes ### 1. `ActionExecutorContext` (index.ts) - Made all properties non-optional - Removed `gitRepoId` from context (now passed as action parameter) - Properties now use `| null` for values that may not exist ### 2. New `GitActionDefinition` Type (index.ts) - Added new action type with `requiresTarget: 'git'` - Git actions now explicitly require `repoId` as a parameter ### 3. Git Actions Updated (index.ts) - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` function takes `repoId` as third parameter - Simplified implementations (no more fallback repo fetching logic) ### 4. `ActionsContext` Provider (ActionsContext.tsx) - Now calls `useDevServer()` to get dev server state - Provides all required context properties - `executeAction` handles three action types: global, workspace, and git - Removed `contextOverride` pattern ### 5. `ContextBarContainer` (ContextBarContainer.tsx) - Simplified significantly - no longer builds extended context - Uses executor context directly from provider - Removed `containerPath` prop ### 6. `ActionVisibilityContext` (index.ts + useActionVisibility.ts) - Made `editorType`, `devServerState`, `runningDevServerId` required - Visibility hook now provides all values from hooks ### 7. Updated Call Sites - `WorkspacesLayout`: Git actions now pass `repoId` as third parameter - `WorkspacesMain`: Removed `containerPath` prop from ContextBarContainer * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * All changes are complete and type checks pass. Here's a summary of the changes made: ## Summary of Changes ### 1. `ActionExecutorContext` - Made all properties non-optional - Removed `?` from all properties - Removed `gitRepoId` (moved to per-action parameter) - Changed nullable properties to use `| null` type ### 2. New `GitActionDefinition` type - Added new action type with `requiresTarget: 'git'` - Takes `repoId` as explicit third parameter ### 3. Git actions updated - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` receives `repoId` as parameter ### 4. `ActionsContext` provider centralized - Now calls `useDevServer()` to provide dev server state - Provides all required context properties from hooks - `executeAction` handles three action types: global, workspace, git - Removed `contextOverride` pattern ### 5. `ActionVisibilityContext` - Made dev server properties required - `editorType`, `devServerState`, `runningDevServerId` now required - `useActionVisibilityContext` hook provides all values ### 6. `ContextBarContainer` simplified - Removed extended context creation - Uses executor context directly from provider ### 7. Provider order fix - Moved `ExecutionProcessesProvider` up to `NewDesignScope` - Required because `useDevServer` depends on `ExecutionProcessesContext` ### Files Modified: - `vibe-kanban/frontend/src/components/ui-new/actions/index.ts` - `vibe-kanban/frontend/src/components/ui-new/actions/useActionVisibility.ts` - `vibe-kanban/frontend/src/contexts/ActionsContext.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/ContextBarContainer.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx` - `vibe-kanban/frontend/src/components/ui-new/views/WorkspacesMain.tsx` - `vibe-kanban/frontend/src/components/ui-new/scope/NewDesignScope.tsx` * The implementation is complete. Let me summarize what was changed: ## Summary of Changes ### `pages.ts` - Added `'selectRepo'` to `PageId` type for dynamic repo selection page - Created `StaticPageId` type (excludes `selectRepo`) for static page records - Added `RepoItem` interface for repo selection items - Extended `ResolvedGroupItem` union type to include `{ type: 'repo'; repo: RepoItem }` - Updated `Pages` record and `getPageActions` to use `StaticPageId` ### `CommandBarDialog.tsx` - Added `repos` from `useWorkspaceContext()` - Added `pendingGitAction` state to track git actions awaiting repo selection - Reset `pendingGitAction` when dialog opens/closes - Modified `getPageWithItems` to generate dynamic `selectRepo` page with repo list - Updated `handleSelect` to: - Execute git actions immediately with single repo - Navigate to `selectRepo` page when multiple repos exist - Execute pending git action when user selects a repo - Clear `pendingGitAction` when navigating back from `selectRepo` page ### `CommandBar.tsx` - Added `FolderIcon` import - Added rendering for `'repo'` item type with folder icon ## Behavior 1. **Single repo**: When user selects a git action (e.g., "Create Pull Request") and the workspace has only one git repo, the action executes immediately with that repo's ID. 2. **Multiple repos**: When workspace has multiple repos, selecting a git action navigates to a "Select Repository" page where the user can choose which repo to use, then the action executes with the selected repo. 3. **Back navigation**: Pressing back from the repo selection page clears the pending action and returns to the previous page. * Done! The `CommandBarDialog` has been refactored to use a state machine pattern with `useReducer`. Here's a summary of the changes: ## Summary of Changes **State Machine Types (lines 38-67):** - `CommandBarState`: Discriminated union with `browsing` and `selectingRepo` states - `CommandBarEvent`: `RESET`, `SEARCH_CHANGE`, `GO_BACK`, `SELECT_ITEM` - `CommandBarEffect`: Side effects returned from reducer (`none` or `execute`) **Reducer (lines 73-201):** - Pure function returning `[newState, effect]` tuple - Handles all state transitions based on current state and event - Git action flow: single repo → execute, multiple repos → transition to `selectingRepo` **Component Changes:** - Replaced 4 `useState` calls with single `useReducer` - `currentPage` is now derived from state (line 249-250) - `handleSelect` computes effect from reducer, dispatches event, then handles effect (lines 456-475) - `goBack` and `handleSearchChange` simply dispatch events (lines 446-453) **Benefits:** - All state transitions in one place (the reducer) - State transitions are pure and testable - Clear separation between state updates and side effects - Impossible states are unrepresentable (e.g., can't have `pendingAction` while `browsing`) * Done! Here's the final result: ## Summary **Before:** 520 lines in 1 file (after initial state machine refactor) **After:** 487 lines across 4 files | File | Lines | Purpose | |------|-------|---------| | `CommandBarDialog.tsx` | 101 | Thin orchestration (down from 520) | | `useCommandBarState.ts` | 177 | State machine hook | | `useResolvedPage.ts` | 150 | Page building logic | | `injectSearchMatches.ts` | 59 | DRY search helper | **Key wins:** - Main component: **520 → 101 lines** (80% reduction) - Search injection code: **100 → 59 lines** (4× duplication eliminated) - Each file has single responsibility - State machine is testable in isolation - Page building logic is reusable * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * Done! Here are the final results: ## Final Summary | File | Before | After | Reduction | |------|--------|-------|-----------| | `CommandBarDialog.tsx` | 101 | 106 | +5 (linter formatting) | | `useCommandBarState.ts` | 177 | 95 | **-82 (46%)** | | `useResolvedPage.ts` | 150 | 95 | **-55 (37%)** | | `injectSearchMatches.ts` | 59 | 42 | **-17 (29%)** | | **Total** | **487** | **338** | **-149 (31%)** | **Original single file:** 398 lines **Final 4 files:** 338 lines (15% less code overall) Key wins: - Main component is now just 106 lines of thin orchestration - State machine is flattened (if statements instead of nested switch/case) - Page resolution is simplified with reused types - Search injection uses reduce() instead of for-loop - All files pass type check * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * I've fixed both issues with the CommandBar. Here's a summary of what was wrong and how I fixed it: ## Root Causes ### Issue 1: Nested `<Command>` Components The `CommandDialog` component was wrapping children in a `<Command>` component, and `CommandBar` also rendered its own `<Command>`. This nested cmdk context caused conflicts with state management. **Fix:** Removed the `<Command>` wrapper from `CommandDialog` in `Command.tsx:26-44`, and moved the styling classes to `CommandBar`'s `<Command>` component. ### Issue 2: Unstable `dispatch` Function Causing Infinite RESET Loop In `useCommandBarState.ts`, the `dispatch` function had `[state, repoCount]` in its `useCallback` dependency array. This caused `dispatch` to be recreated on every state change. In `CommandBarDialog.tsx`, the `useEffect` that resets state when the modal opens had `dispatch` in its dependency array: ```tsx useEffect(() => { if (modal.visible) { dispatch({ type: 'RESET', page }); } }, [modal.visible, page, dispatch]); // dispatch changes → useEffect runs → RESET ``` This created an infinite loop: 1. User types → state updates → `dispatch` recreated 2. `dispatch` changed → `useEffect` runs → `RESET` dispatched 3. State resets to empty → repeat **Fix:** Made `dispatch` stable by using refs instead of state in dependencies: ```tsx const stateRef = useRef<CommandBarState>(browsing(initialPage)); const repoCountRef = useRef(repoCount); const dispatch = useCallback( (event: CommandBarEvent): CommandBarEffect => { const [, effect] = reducer(stateRef.current, event, repoCountRef.current); rawDispatch(event); return effect; }, [] // No dependencies - uses refs for current values ); ``` ## Files Changed 1. `vibe-kanban/frontend/src/components/ui-new/primitives/Command.tsx` - Removed nested `<Command>` wrapper from `CommandDialog` 2. `vibe-kanban/frontend/src/components/ui-new/primitives/CommandBar.tsx` - Added styling classes that were on the removed wrapper 3. `vibe-kanban/frontend/src/components/ui-new/dialogs/commandBar/useCommandBarState.ts` - Made `dispatch` stable using refs
2026-01-09 16:25:42 +00:00
const { workspace, workspaceId, isCreateMode, repos } = useWorkspaceContext();
2026-01-08 22:14:38 +00:00
const diffPaths = useDiffViewStore((s) => s.diffPaths);
const diffViewMode = useDiffViewMode();
const expanded = useUiPreferencesStore((s) => s.expanded);
Fix CommandBar input and navigation issues with state machine refactor (Vibe Kanban) (#1885) * Done! I've removed all 7 `shortcut` properties from the action definitions in `index.ts`: - `NewWorkspace`: removed `shortcut: 'N'` - `Settings`: removed `shortcut: ','` - `ToggleSidebar`: removed `shortcut: '['` - `ToggleGitPanel`: removed `shortcut: ']'` - `ToggleChangesMode`: removed `shortcut: 'C'` - `ToggleLogsMode`: removed `shortcut: 'L'` - `TogglePreviewMode`: removed `shortcut: 'P'` The `ActionDefinition` type (and its base `ActionBase` interface) were left unchanged as requested. * fix tailwind nto auto creating styles * I've successfully completed all the changes. Here's a summary of what was done: ## Summary of Changes ### 1. `ActionExecutorContext` (index.ts) - Made all properties non-optional - Removed `gitRepoId` from context (now passed as action parameter) - Properties now use `| null` for values that may not exist ### 2. New `GitActionDefinition` Type (index.ts) - Added new action type with `requiresTarget: 'git'` - Git actions now explicitly require `repoId` as a parameter ### 3. Git Actions Updated (index.ts) - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` function takes `repoId` as third parameter - Simplified implementations (no more fallback repo fetching logic) ### 4. `ActionsContext` Provider (ActionsContext.tsx) - Now calls `useDevServer()` to get dev server state - Provides all required context properties - `executeAction` handles three action types: global, workspace, and git - Removed `contextOverride` pattern ### 5. `ContextBarContainer` (ContextBarContainer.tsx) - Simplified significantly - no longer builds extended context - Uses executor context directly from provider - Removed `containerPath` prop ### 6. `ActionVisibilityContext` (index.ts + useActionVisibility.ts) - Made `editorType`, `devServerState`, `runningDevServerId` required - Visibility hook now provides all values from hooks ### 7. Updated Call Sites - `WorkspacesLayout`: Git actions now pass `repoId` as third parameter - `WorkspacesMain`: Removed `containerPath` prop from ContextBarContainer * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * All changes are complete and type checks pass. Here's a summary of the changes made: ## Summary of Changes ### 1. `ActionExecutorContext` - Made all properties non-optional - Removed `?` from all properties - Removed `gitRepoId` (moved to per-action parameter) - Changed nullable properties to use `| null` type ### 2. New `GitActionDefinition` type - Added new action type with `requiresTarget: 'git'` - Takes `repoId` as explicit third parameter ### 3. Git actions updated - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` receives `repoId` as parameter ### 4. `ActionsContext` provider centralized - Now calls `useDevServer()` to provide dev server state - Provides all required context properties from hooks - `executeAction` handles three action types: global, workspace, git - Removed `contextOverride` pattern ### 5. `ActionVisibilityContext` - Made dev server properties required - `editorType`, `devServerState`, `runningDevServerId` now required - `useActionVisibilityContext` hook provides all values ### 6. `ContextBarContainer` simplified - Removed extended context creation - Uses executor context directly from provider ### 7. Provider order fix - Moved `ExecutionProcessesProvider` up to `NewDesignScope` - Required because `useDevServer` depends on `ExecutionProcessesContext` ### Files Modified: - `vibe-kanban/frontend/src/components/ui-new/actions/index.ts` - `vibe-kanban/frontend/src/components/ui-new/actions/useActionVisibility.ts` - `vibe-kanban/frontend/src/contexts/ActionsContext.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/ContextBarContainer.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx` - `vibe-kanban/frontend/src/components/ui-new/views/WorkspacesMain.tsx` - `vibe-kanban/frontend/src/components/ui-new/scope/NewDesignScope.tsx` * The implementation is complete. Let me summarize what was changed: ## Summary of Changes ### `pages.ts` - Added `'selectRepo'` to `PageId` type for dynamic repo selection page - Created `StaticPageId` type (excludes `selectRepo`) for static page records - Added `RepoItem` interface for repo selection items - Extended `ResolvedGroupItem` union type to include `{ type: 'repo'; repo: RepoItem }` - Updated `Pages` record and `getPageActions` to use `StaticPageId` ### `CommandBarDialog.tsx` - Added `repos` from `useWorkspaceContext()` - Added `pendingGitAction` state to track git actions awaiting repo selection - Reset `pendingGitAction` when dialog opens/closes - Modified `getPageWithItems` to generate dynamic `selectRepo` page with repo list - Updated `handleSelect` to: - Execute git actions immediately with single repo - Navigate to `selectRepo` page when multiple repos exist - Execute pending git action when user selects a repo - Clear `pendingGitAction` when navigating back from `selectRepo` page ### `CommandBar.tsx` - Added `FolderIcon` import - Added rendering for `'repo'` item type with folder icon ## Behavior 1. **Single repo**: When user selects a git action (e.g., "Create Pull Request") and the workspace has only one git repo, the action executes immediately with that repo's ID. 2. **Multiple repos**: When workspace has multiple repos, selecting a git action navigates to a "Select Repository" page where the user can choose which repo to use, then the action executes with the selected repo. 3. **Back navigation**: Pressing back from the repo selection page clears the pending action and returns to the previous page. * Done! The `CommandBarDialog` has been refactored to use a state machine pattern with `useReducer`. Here's a summary of the changes: ## Summary of Changes **State Machine Types (lines 38-67):** - `CommandBarState`: Discriminated union with `browsing` and `selectingRepo` states - `CommandBarEvent`: `RESET`, `SEARCH_CHANGE`, `GO_BACK`, `SELECT_ITEM` - `CommandBarEffect`: Side effects returned from reducer (`none` or `execute`) **Reducer (lines 73-201):** - Pure function returning `[newState, effect]` tuple - Handles all state transitions based on current state and event - Git action flow: single repo → execute, multiple repos → transition to `selectingRepo` **Component Changes:** - Replaced 4 `useState` calls with single `useReducer` - `currentPage` is now derived from state (line 249-250) - `handleSelect` computes effect from reducer, dispatches event, then handles effect (lines 456-475) - `goBack` and `handleSearchChange` simply dispatch events (lines 446-453) **Benefits:** - All state transitions in one place (the reducer) - State transitions are pure and testable - Clear separation between state updates and side effects - Impossible states are unrepresentable (e.g., can't have `pendingAction` while `browsing`) * Done! Here's the final result: ## Summary **Before:** 520 lines in 1 file (after initial state machine refactor) **After:** 487 lines across 4 files | File | Lines | Purpose | |------|-------|---------| | `CommandBarDialog.tsx` | 101 | Thin orchestration (down from 520) | | `useCommandBarState.ts` | 177 | State machine hook | | `useResolvedPage.ts` | 150 | Page building logic | | `injectSearchMatches.ts` | 59 | DRY search helper | **Key wins:** - Main component: **520 → 101 lines** (80% reduction) - Search injection code: **100 → 59 lines** (4× duplication eliminated) - Each file has single responsibility - State machine is testable in isolation - Page building logic is reusable * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * Done! Here are the final results: ## Final Summary | File | Before | After | Reduction | |------|--------|-------|-----------| | `CommandBarDialog.tsx` | 101 | 106 | +5 (linter formatting) | | `useCommandBarState.ts` | 177 | 95 | **-82 (46%)** | | `useResolvedPage.ts` | 150 | 95 | **-55 (37%)** | | `injectSearchMatches.ts` | 59 | 42 | **-17 (29%)** | | **Total** | **487** | **338** | **-149 (31%)** | **Original single file:** 398 lines **Final 4 files:** 338 lines (15% less code overall) Key wins: - Main component is now just 106 lines of thin orchestration - State machine is flattened (if statements instead of nested switch/case) - Page resolution is simplified with reused types - Search injection uses reduce() instead of for-loop - All files pass type check * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * I've fixed both issues with the CommandBar. Here's a summary of what was wrong and how I fixed it: ## Root Causes ### Issue 1: Nested `<Command>` Components The `CommandDialog` component was wrapping children in a `<Command>` component, and `CommandBar` also rendered its own `<Command>`. This nested cmdk context caused conflicts with state management. **Fix:** Removed the `<Command>` wrapper from `CommandDialog` in `Command.tsx:26-44`, and moved the styling classes to `CommandBar`'s `<Command>` component. ### Issue 2: Unstable `dispatch` Function Causing Infinite RESET Loop In `useCommandBarState.ts`, the `dispatch` function had `[state, repoCount]` in its `useCallback` dependency array. This caused `dispatch` to be recreated on every state change. In `CommandBarDialog.tsx`, the `useEffect` that resets state when the modal opens had `dispatch` in its dependency array: ```tsx useEffect(() => { if (modal.visible) { dispatch({ type: 'RESET', page }); } }, [modal.visible, page, dispatch]); // dispatch changes → useEffect runs → RESET ``` This created an infinite loop: 1. User types → state updates → `dispatch` recreated 2. `dispatch` changed → `useEffect` runs → `RESET` dispatched 3. State resets to empty → repeat **Fix:** Made `dispatch` stable by using refs instead of state in dependencies: ```tsx const stateRef = useRef<CommandBarState>(browsing(initialPage)); const repoCountRef = useRef(repoCount); const dispatch = useCallback( (event: CommandBarEvent): CommandBarEffect => { const [, effect] = reducer(stateRef.current, event, repoCountRef.current); rawDispatch(event); return effect; }, [] // No dependencies - uses refs for current values ); ``` ## Files Changed 1. `vibe-kanban/frontend/src/components/ui-new/primitives/Command.tsx` - Removed nested `<Command>` wrapper from `CommandDialog` 2. `vibe-kanban/frontend/src/components/ui-new/primitives/CommandBar.tsx` - Added styling classes that were on the removed wrapper 3. `vibe-kanban/frontend/src/components/ui-new/dialogs/commandBar/useCommandBarState.ts` - Made `dispatch` stable using refs
2026-01-09 16:25:42 +00:00
const { config } = useUserSystem();
const { isStarting, isStopping, runningDevServers } =
Fix CommandBar input and navigation issues with state machine refactor (Vibe Kanban) (#1885) * Done! I've removed all 7 `shortcut` properties from the action definitions in `index.ts`: - `NewWorkspace`: removed `shortcut: 'N'` - `Settings`: removed `shortcut: ','` - `ToggleSidebar`: removed `shortcut: '['` - `ToggleGitPanel`: removed `shortcut: ']'` - `ToggleChangesMode`: removed `shortcut: 'C'` - `ToggleLogsMode`: removed `shortcut: 'L'` - `TogglePreviewMode`: removed `shortcut: 'P'` The `ActionDefinition` type (and its base `ActionBase` interface) were left unchanged as requested. * fix tailwind nto auto creating styles * I've successfully completed all the changes. Here's a summary of what was done: ## Summary of Changes ### 1. `ActionExecutorContext` (index.ts) - Made all properties non-optional - Removed `gitRepoId` from context (now passed as action parameter) - Properties now use `| null` for values that may not exist ### 2. New `GitActionDefinition` Type (index.ts) - Added new action type with `requiresTarget: 'git'` - Git actions now explicitly require `repoId` as a parameter ### 3. Git Actions Updated (index.ts) - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` function takes `repoId` as third parameter - Simplified implementations (no more fallback repo fetching logic) ### 4. `ActionsContext` Provider (ActionsContext.tsx) - Now calls `useDevServer()` to get dev server state - Provides all required context properties - `executeAction` handles three action types: global, workspace, and git - Removed `contextOverride` pattern ### 5. `ContextBarContainer` (ContextBarContainer.tsx) - Simplified significantly - no longer builds extended context - Uses executor context directly from provider - Removed `containerPath` prop ### 6. `ActionVisibilityContext` (index.ts + useActionVisibility.ts) - Made `editorType`, `devServerState`, `runningDevServerId` required - Visibility hook now provides all values from hooks ### 7. Updated Call Sites - `WorkspacesLayout`: Git actions now pass `repoId` as third parameter - `WorkspacesMain`: Removed `containerPath` prop from ContextBarContainer * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * All changes are complete and type checks pass. Here's a summary of the changes made: ## Summary of Changes ### 1. `ActionExecutorContext` - Made all properties non-optional - Removed `?` from all properties - Removed `gitRepoId` (moved to per-action parameter) - Changed nullable properties to use `| null` type ### 2. New `GitActionDefinition` type - Added new action type with `requiresTarget: 'git'` - Takes `repoId` as explicit third parameter ### 3. Git actions updated - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` receives `repoId` as parameter ### 4. `ActionsContext` provider centralized - Now calls `useDevServer()` to provide dev server state - Provides all required context properties from hooks - `executeAction` handles three action types: global, workspace, git - Removed `contextOverride` pattern ### 5. `ActionVisibilityContext` - Made dev server properties required - `editorType`, `devServerState`, `runningDevServerId` now required - `useActionVisibilityContext` hook provides all values ### 6. `ContextBarContainer` simplified - Removed extended context creation - Uses executor context directly from provider ### 7. Provider order fix - Moved `ExecutionProcessesProvider` up to `NewDesignScope` - Required because `useDevServer` depends on `ExecutionProcessesContext` ### Files Modified: - `vibe-kanban/frontend/src/components/ui-new/actions/index.ts` - `vibe-kanban/frontend/src/components/ui-new/actions/useActionVisibility.ts` - `vibe-kanban/frontend/src/contexts/ActionsContext.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/ContextBarContainer.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx` - `vibe-kanban/frontend/src/components/ui-new/views/WorkspacesMain.tsx` - `vibe-kanban/frontend/src/components/ui-new/scope/NewDesignScope.tsx` * The implementation is complete. Let me summarize what was changed: ## Summary of Changes ### `pages.ts` - Added `'selectRepo'` to `PageId` type for dynamic repo selection page - Created `StaticPageId` type (excludes `selectRepo`) for static page records - Added `RepoItem` interface for repo selection items - Extended `ResolvedGroupItem` union type to include `{ type: 'repo'; repo: RepoItem }` - Updated `Pages` record and `getPageActions` to use `StaticPageId` ### `CommandBarDialog.tsx` - Added `repos` from `useWorkspaceContext()` - Added `pendingGitAction` state to track git actions awaiting repo selection - Reset `pendingGitAction` when dialog opens/closes - Modified `getPageWithItems` to generate dynamic `selectRepo` page with repo list - Updated `handleSelect` to: - Execute git actions immediately with single repo - Navigate to `selectRepo` page when multiple repos exist - Execute pending git action when user selects a repo - Clear `pendingGitAction` when navigating back from `selectRepo` page ### `CommandBar.tsx` - Added `FolderIcon` import - Added rendering for `'repo'` item type with folder icon ## Behavior 1. **Single repo**: When user selects a git action (e.g., "Create Pull Request") and the workspace has only one git repo, the action executes immediately with that repo's ID. 2. **Multiple repos**: When workspace has multiple repos, selecting a git action navigates to a "Select Repository" page where the user can choose which repo to use, then the action executes with the selected repo. 3. **Back navigation**: Pressing back from the repo selection page clears the pending action and returns to the previous page. * Done! The `CommandBarDialog` has been refactored to use a state machine pattern with `useReducer`. Here's a summary of the changes: ## Summary of Changes **State Machine Types (lines 38-67):** - `CommandBarState`: Discriminated union with `browsing` and `selectingRepo` states - `CommandBarEvent`: `RESET`, `SEARCH_CHANGE`, `GO_BACK`, `SELECT_ITEM` - `CommandBarEffect`: Side effects returned from reducer (`none` or `execute`) **Reducer (lines 73-201):** - Pure function returning `[newState, effect]` tuple - Handles all state transitions based on current state and event - Git action flow: single repo → execute, multiple repos → transition to `selectingRepo` **Component Changes:** - Replaced 4 `useState` calls with single `useReducer` - `currentPage` is now derived from state (line 249-250) - `handleSelect` computes effect from reducer, dispatches event, then handles effect (lines 456-475) - `goBack` and `handleSearchChange` simply dispatch events (lines 446-453) **Benefits:** - All state transitions in one place (the reducer) - State transitions are pure and testable - Clear separation between state updates and side effects - Impossible states are unrepresentable (e.g., can't have `pendingAction` while `browsing`) * Done! Here's the final result: ## Summary **Before:** 520 lines in 1 file (after initial state machine refactor) **After:** 487 lines across 4 files | File | Lines | Purpose | |------|-------|---------| | `CommandBarDialog.tsx` | 101 | Thin orchestration (down from 520) | | `useCommandBarState.ts` | 177 | State machine hook | | `useResolvedPage.ts` | 150 | Page building logic | | `injectSearchMatches.ts` | 59 | DRY search helper | **Key wins:** - Main component: **520 → 101 lines** (80% reduction) - Search injection code: **100 → 59 lines** (4× duplication eliminated) - Each file has single responsibility - State machine is testable in isolation - Page building logic is reusable * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * Done! Here are the final results: ## Final Summary | File | Before | After | Reduction | |------|--------|-------|-----------| | `CommandBarDialog.tsx` | 101 | 106 | +5 (linter formatting) | | `useCommandBarState.ts` | 177 | 95 | **-82 (46%)** | | `useResolvedPage.ts` | 150 | 95 | **-55 (37%)** | | `injectSearchMatches.ts` | 59 | 42 | **-17 (29%)** | | **Total** | **487** | **338** | **-149 (31%)** | **Original single file:** 398 lines **Final 4 files:** 338 lines (15% less code overall) Key wins: - Main component is now just 106 lines of thin orchestration - State machine is flattened (if statements instead of nested switch/case) - Page resolution is simplified with reused types - Search injection uses reduce() instead of for-loop - All files pass type check * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * I've fixed both issues with the CommandBar. Here's a summary of what was wrong and how I fixed it: ## Root Causes ### Issue 1: Nested `<Command>` Components The `CommandDialog` component was wrapping children in a `<Command>` component, and `CommandBar` also rendered its own `<Command>`. This nested cmdk context caused conflicts with state management. **Fix:** Removed the `<Command>` wrapper from `CommandDialog` in `Command.tsx:26-44`, and moved the styling classes to `CommandBar`'s `<Command>` component. ### Issue 2: Unstable `dispatch` Function Causing Infinite RESET Loop In `useCommandBarState.ts`, the `dispatch` function had `[state, repoCount]` in its `useCallback` dependency array. This caused `dispatch` to be recreated on every state change. In `CommandBarDialog.tsx`, the `useEffect` that resets state when the modal opens had `dispatch` in its dependency array: ```tsx useEffect(() => { if (modal.visible) { dispatch({ type: 'RESET', page }); } }, [modal.visible, page, dispatch]); // dispatch changes → useEffect runs → RESET ``` This created an infinite loop: 1. User types → state updates → `dispatch` recreated 2. `dispatch` changed → `useEffect` runs → `RESET` dispatched 3. State resets to empty → repeat **Fix:** Made `dispatch` stable by using refs instead of state in dependencies: ```tsx const stateRef = useRef<CommandBarState>(browsing(initialPage)); const repoCountRef = useRef(repoCount); const dispatch = useCallback( (event: CommandBarEvent): CommandBarEffect => { const [, effect] = reducer(stateRef.current, event, repoCountRef.current); rawDispatch(event); return effect; }, [] // No dependencies - uses refs for current values ); ``` ## Files Changed 1. `vibe-kanban/frontend/src/components/ui-new/primitives/Command.tsx` - Removed nested `<Command>` wrapper from `CommandDialog` 2. `vibe-kanban/frontend/src/components/ui-new/primitives/CommandBar.tsx` - Added styling classes that were on the removed wrapper 3. `vibe-kanban/frontend/src/components/ui-new/dialogs/commandBar/useCommandBarState.ts` - Made `dispatch` stable using refs
2026-01-09 16:25:42 +00:00
useDevServer(workspaceId);
Add Push button to Git panel when PR is open with unpushed commits (Vibe Kanban) (#1951) * The implementation is complete. Here's a summary of what I changed: ## Summary Added a "Push" button to the workspaces UI Git panel. The changes allow users to push changes when a PR is already open. ### Files Modified: 1. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`** (lines 27-42) - Added `'push'` to the `RepoAction` type union - Added a new option to `repoActionOptions` array with the Push button configuration using `ArrowUpIcon` 2. **`frontend/src/components/ui-new/actions/index.ts`** (lines 33, 691-707) - Imported `ArrowUpIcon` from phosphor-icons - Added `GitPush` action definition that: - Calls `attemptsApi.push()` with the workspace and repo IDs - Handles the `force_push_required` error case - Invalidates workspace queries on success to refresh the UI 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`** (line 103) - Added mapping from `'push'` action to `Actions.GitPush` in the action handler The Push button now appears in the Git panel dropdown alongside "Open pull request" and "Merge" options. * The type check passes. Here's a summary of the updated changes: ## Summary The Push button now appears **next to the PR number** when: 1. A PR is already open (`prStatus === 'open'`) 2. There are new commits ahead (`commitsAhead > 0`) ### Changes Made: **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Removed `push` from the `repoActionOptions` dropdown (lines 34-41) - Added a dedicated Push button in the PR status row (lines 213-222) that: - Only appears when `prStatus === 'open'` AND `commitsAhead > 0` - Uses brand primary styling (`bg-brand-primary`) to make it prominent - Displays with an up arrow icon and "Push" label using existing translation - Calls `onActionsClick?.('push')` when clicked The `GitPush` action and handler mapping from the previous changes remain in place to handle the push operation. * Done. The Push button now uses the same styling as the "Open PR" button: `bg-panel text-normal hover:bg-tertiary`. * The type check passes. Here's a summary of the fix: ## Summary The Push button now correctly uses `remoteCommitsAhead` (unpushed commits to remote) instead of `commitsAhead` (total commits ahead of target branch) to determine when to show. ### Changes Made: 1. **`frontend/src/components/ui-new/views/GitPanel.tsx`**: - Added `remoteCommitsAhead: number` to the `RepoInfo` interface - Pass `remoteCommitsAhead` to `RepoCard` 2. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Added `remoteCommitsAhead?: number` to `RepoCardProps` - Changed the push button condition from `commitsAhead > 0` to `remoteCommitsAhead > 0` 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`**: - Added `remoteCommitsAhead: repoStatus?.remote_commits_ahead ?? 0` to the `repoInfos` mapping Now the Push button will: - Only appear when there's an open PR AND there are unpushed commits (`remote_commits_ahead > 0`) - Disappear after a successful push (when `remote_commits_ahead` becomes 0) - Reappear when new commits are made locally that haven't been pushed * The type check passes. Here's a summary of the changes: ## Summary The Push button now hides immediately when clicked and only reappears when new commits are available: ### Changes to `RepoCard.tsx`: - Added `useState` and `useRef` imports - Added `isPushHidden` state to track when the button should be hidden - Added `lastRemoteCommitsAhead` ref to track the previous value - Added `useEffect` that resets `isPushHidden` to `false` when `remoteCommitsAhead` increases (meaning new commits are available) - Added `handlePushClick` function that hides the button immediately and triggers the push action - Updated the button condition to include `!isPushHidden` The button will: 1. Hide immediately when clicked (no double-clicking possible) 2. Stay hidden until `remoteCommitsAhead` increases above the last known value 3. Reappear when new local commits are made that need to be pushed * Both the linter and type check pass. The linter errors are fixed. Here's a summary of the changes: ## Summary of Linter Fixes The linter was flagging that `RepoCard` (a presentational component) should not manage state or have side effects. I refactored the code to lift the state up to the container component: ### Changes: 1. **`RepoCard.tsx`** - Made purely presentational: - Removed `useState`, `useRef`, `useEffect` imports - Removed `remoteCommitsAhead` prop - Added `showPushButton` prop (boolean, controlled by parent) - Added `onPushClick` callback prop - The button now just renders based on `showPushButton` and calls `onPushClick` 2. **`GitPanel.tsx`** - Updated interface and props: - `RepoInfo` now has optional `remoteCommitsAhead` and `showPushButton` fields - Added `onPushClick` callback to `GitPanelProps` - Passes `showPushButton` and `onPushClick` to `RepoCard` 3. **`WorkspacesLayout.tsx`** - State management moved here: - Added `hiddenPushRepos` state (Set of repo IDs where push was clicked) - Added `lastRemoteCommitsAhead` ref to track previous values - Added `useEffect` to reset hidden state when `remoteCommitsAhead` increases - Added `repoInfosWithPushButton` memo that computes `showPushButton` for each repo - Added `handlePushClick` that hides button immediately and executes push action * prettier fix * Done. I've added `GitPush` to the Git actions list in the command bar. It now appears between "Merge" and "Rebase" in the Git Actions menu. * Both type check and lint pass. Done! The Push action in the command bar now has the same visibility conditions as in the Git panel - it only appears when: 1. There's a workspace 2. There are git repos 3. There's an open PR (`hasOpenPR`) 4. There are unpushed commits (`hasUnpushedCommits`) ### Summary of changes: 1. **`frontend/src/components/ui-new/actions/index.ts`**: - Added `hasOpenPR` and `hasUnpushedCommits` to `ActionVisibilityContext` - Updated `GitPush.isVisible` to check these new context properties 2. **`frontend/src/components/ui-new/actions/useActionVisibility.ts`**: - Added `useBranchStatus` hook to get branch status data - Computed `hasOpenPR` by checking if any repo has an open PR in merges - Computed `hasUnpushedCommits` by checking if any repo has `remote_commits_ahead > 0` - Added these values to the returned context * The refactoring is complete. Here's a summary of the changes: ## Summary of Changes The push button logic has been simplified by using React Query's mutation state instead of manually tracking hidden state with refs and effects. ### Key Changes: 1. **WorkspacesLayout.tsx (`GitPanelContainer`)**: - Removed the complex `useEffect` that tracked `lastRemoteCommitsAhead` and managed `hiddenPushRepos` state - Removed `hiddenPushRepos` state and `lastRemoteCommitsAhead` ref - Added `usePush` hook with callbacks that: - On success: awaits query invalidation before clearing the loading state - On error: clears the loading state - Added simple `pushingRepoId` state to track which repo is being pushed - The button now shows loading state during push and automatically disappears after refetch if `remoteCommitsAhead` becomes 0 2. **GitPanel.tsx**: - Added `isPushPending?: boolean` to `RepoInfo` interface - Passes `isPushPending` to `RepoCard` 3. **RepoCard.tsx**: - Added `isPushPending` prop - Button shows while either `showPushButton` or `isPushPending` is true - Button is disabled and shows spinner with "Pushing..." text when pending - Uses existing translation key `git.states.pushing` ### How it works now: 1. User clicks push button 2. Button immediately shows loading state (spinner + "Pushing...") 3. Button is disabled to prevent multiple clicks 4. Push mutation runs 5. On success, query invalidation completes before clearing loading state 6. Once `branchStatus` refetches, if `remoteCommitsAhead` is 0, `showPushButton` becomes false and button disappears 7. On error, loading state clears and button returns to normal This approach is simpler and more predictable than manually tracking previous values. * Done. The push button now shows success feedback like the old UI: ## Summary of Changes The push button now has three states: 1. **Normal**: Shows "Push" with an up arrow icon 2. **Pending**: Shows "Pushing..." with a spinning icon, button is disabled 3. **Success**: Shows "Pushed!" with a green checkmark icon and green background for 2 seconds The flow is: 1. User clicks "Push" 2. Button immediately shows "Pushing..." with spinner 3. Push completes, queries are invalidated and refetched 4. Button shows green "Pushed!" for 2 seconds 5. Button disappears (if no more commits to push) or returns to normal state * All code review issues have been addressed: ## Summary of Changes 1. **Unified push logic** - Now uses `executeAction(Actions.GitPush, ...)` instead of calling `usePush` hook directly. This ensures consistent error handling between the button and command bar. 2. **Proper setTimeout cleanup** - Added `successTimeoutRef` with a cleanup effect that clears the timeout when the component unmounts: ```typescript const successTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); useEffect(() => { return () => { if (successTimeoutRef.current) { clearTimeout(successTimeoutRef.current); } }; }, []); ``` 3. **Error feedback** - Added error state handling: - New `isPushError` prop flows through GitPanel to RepoCard - Button shows red "Failed" with warning icon when push fails - Error state clears after 3 seconds - Added `git.states.pushFailed` translation key 4. **Simplified state management** - Changed from separate `pushingRepoId` and `pushSuccessRepoId` to a single `pushStates` record with `PushState` type (`'idle' | 'pending' | 'success' | 'error'`) * All i18n checks pass now. * Both code review issues have been addressed: ## Summary of Changes ### 1. Fixed stale closure issue - Added a `pushStatesRef` that mirrors the `pushStates` state - Changed the check from `pushStates[repoId]` to `pushStatesRef.current[repoId]` - Removed `pushStates` from the dependency array ### 2. Added proper error logging and user-facing error message - Modified `ActionsContext.tsx` to re-throw errors after showing the dialog, so callers can handle them - Added `console.error('Push failed:', error)` for debugging purposes - The error dialog is already shown by `executeAction` via `ConfirmDialog` - The button now properly shows the error state when push fails * fmt * The fix is in place. The key change is: - When we're in a "push flow" (state is `pending`, `success`, or `error`), we set `showPushButton: false` but the button remains visible because of the `isPushPending || isPushSuccess || isPushError` condition in RepoCard - This prevents the race condition where the query refetch could hide the button before we set the success state * All checks pass. The changes now: 1. **Reverted ActionsContext** - Removed the re-throw to maintain backwards compatibility 2. **Use `usePush` hook directly** - For proper success/error detection instead of going through `executeAction` 3. **Show error dialog manually** - Using `ConfirmDialog.show()` when push fails 4. **Track current repo with ref** - `currentPushRepoRef` tracks which repo is being pushed so callbacks can update the correct state * All checks pass. Here's a summary of the force push handling: ## Force Push Functionality Yes, force push is fully supported. Here's how it works: **Old UI (`useGitOperations.ts`):** 1. When `usePush` fails with `errorData.type === 'force_push_required'` 2. It automatically shows `ForcePushDialog` 3. The dialog warns the user about the consequences and lets them confirm 4. If confirmed, it calls `useForcePush` to execute the force push **New UI (now updated):** 1. When the push button is clicked and fails with `force_push_required` 2. The push state is reset to `idle` (so the button returns to normal) 3. `ForcePushDialog` is shown with the attemptId and repoId 4. User can confirm or cancel the force push from the dialog The `ForcePushDialog` handles its own loading state, success/error feedback, and API call internally via `useForcePush` hook. * clear pushStates on workspace change Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 15:13:57 +01:00
const { data: branchStatus } = useBranchStatus(workspaceId);
const { isAttemptRunningVisible } = useExecutionProcessesContext();
2026-01-08 22:14:38 +00:00
return useMemo(() => {
// Compute isAllDiffsExpanded
const diffKeys = diffPaths.map((p) => `diff:${p}`);
const isAllDiffsExpanded =
diffKeys.length > 0 && diffKeys.every((k) => expanded[k] !== false);
Fix CommandBar input and navigation issues with state machine refactor (Vibe Kanban) (#1885) * Done! I've removed all 7 `shortcut` properties from the action definitions in `index.ts`: - `NewWorkspace`: removed `shortcut: 'N'` - `Settings`: removed `shortcut: ','` - `ToggleSidebar`: removed `shortcut: '['` - `ToggleGitPanel`: removed `shortcut: ']'` - `ToggleChangesMode`: removed `shortcut: 'C'` - `ToggleLogsMode`: removed `shortcut: 'L'` - `TogglePreviewMode`: removed `shortcut: 'P'` The `ActionDefinition` type (and its base `ActionBase` interface) were left unchanged as requested. * fix tailwind nto auto creating styles * I've successfully completed all the changes. Here's a summary of what was done: ## Summary of Changes ### 1. `ActionExecutorContext` (index.ts) - Made all properties non-optional - Removed `gitRepoId` from context (now passed as action parameter) - Properties now use `| null` for values that may not exist ### 2. New `GitActionDefinition` Type (index.ts) - Added new action type with `requiresTarget: 'git'` - Git actions now explicitly require `repoId` as a parameter ### 3. Git Actions Updated (index.ts) - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` function takes `repoId` as third parameter - Simplified implementations (no more fallback repo fetching logic) ### 4. `ActionsContext` Provider (ActionsContext.tsx) - Now calls `useDevServer()` to get dev server state - Provides all required context properties - `executeAction` handles three action types: global, workspace, and git - Removed `contextOverride` pattern ### 5. `ContextBarContainer` (ContextBarContainer.tsx) - Simplified significantly - no longer builds extended context - Uses executor context directly from provider - Removed `containerPath` prop ### 6. `ActionVisibilityContext` (index.ts + useActionVisibility.ts) - Made `editorType`, `devServerState`, `runningDevServerId` required - Visibility hook now provides all values from hooks ### 7. Updated Call Sites - `WorkspacesLayout`: Git actions now pass `repoId` as third parameter - `WorkspacesMain`: Removed `containerPath` prop from ContextBarContainer * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * All changes are complete and type checks pass. Here's a summary of the changes made: ## Summary of Changes ### 1. `ActionExecutorContext` - Made all properties non-optional - Removed `?` from all properties - Removed `gitRepoId` (moved to per-action parameter) - Changed nullable properties to use `| null` type ### 2. New `GitActionDefinition` type - Added new action type with `requiresTarget: 'git'` - Takes `repoId` as explicit third parameter ### 3. Git actions updated - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` receives `repoId` as parameter ### 4. `ActionsContext` provider centralized - Now calls `useDevServer()` to provide dev server state - Provides all required context properties from hooks - `executeAction` handles three action types: global, workspace, git - Removed `contextOverride` pattern ### 5. `ActionVisibilityContext` - Made dev server properties required - `editorType`, `devServerState`, `runningDevServerId` now required - `useActionVisibilityContext` hook provides all values ### 6. `ContextBarContainer` simplified - Removed extended context creation - Uses executor context directly from provider ### 7. Provider order fix - Moved `ExecutionProcessesProvider` up to `NewDesignScope` - Required because `useDevServer` depends on `ExecutionProcessesContext` ### Files Modified: - `vibe-kanban/frontend/src/components/ui-new/actions/index.ts` - `vibe-kanban/frontend/src/components/ui-new/actions/useActionVisibility.ts` - `vibe-kanban/frontend/src/contexts/ActionsContext.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/ContextBarContainer.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx` - `vibe-kanban/frontend/src/components/ui-new/views/WorkspacesMain.tsx` - `vibe-kanban/frontend/src/components/ui-new/scope/NewDesignScope.tsx` * The implementation is complete. Let me summarize what was changed: ## Summary of Changes ### `pages.ts` - Added `'selectRepo'` to `PageId` type for dynamic repo selection page - Created `StaticPageId` type (excludes `selectRepo`) for static page records - Added `RepoItem` interface for repo selection items - Extended `ResolvedGroupItem` union type to include `{ type: 'repo'; repo: RepoItem }` - Updated `Pages` record and `getPageActions` to use `StaticPageId` ### `CommandBarDialog.tsx` - Added `repos` from `useWorkspaceContext()` - Added `pendingGitAction` state to track git actions awaiting repo selection - Reset `pendingGitAction` when dialog opens/closes - Modified `getPageWithItems` to generate dynamic `selectRepo` page with repo list - Updated `handleSelect` to: - Execute git actions immediately with single repo - Navigate to `selectRepo` page when multiple repos exist - Execute pending git action when user selects a repo - Clear `pendingGitAction` when navigating back from `selectRepo` page ### `CommandBar.tsx` - Added `FolderIcon` import - Added rendering for `'repo'` item type with folder icon ## Behavior 1. **Single repo**: When user selects a git action (e.g., "Create Pull Request") and the workspace has only one git repo, the action executes immediately with that repo's ID. 2. **Multiple repos**: When workspace has multiple repos, selecting a git action navigates to a "Select Repository" page where the user can choose which repo to use, then the action executes with the selected repo. 3. **Back navigation**: Pressing back from the repo selection page clears the pending action and returns to the previous page. * Done! The `CommandBarDialog` has been refactored to use a state machine pattern with `useReducer`. Here's a summary of the changes: ## Summary of Changes **State Machine Types (lines 38-67):** - `CommandBarState`: Discriminated union with `browsing` and `selectingRepo` states - `CommandBarEvent`: `RESET`, `SEARCH_CHANGE`, `GO_BACK`, `SELECT_ITEM` - `CommandBarEffect`: Side effects returned from reducer (`none` or `execute`) **Reducer (lines 73-201):** - Pure function returning `[newState, effect]` tuple - Handles all state transitions based on current state and event - Git action flow: single repo → execute, multiple repos → transition to `selectingRepo` **Component Changes:** - Replaced 4 `useState` calls with single `useReducer` - `currentPage` is now derived from state (line 249-250) - `handleSelect` computes effect from reducer, dispatches event, then handles effect (lines 456-475) - `goBack` and `handleSearchChange` simply dispatch events (lines 446-453) **Benefits:** - All state transitions in one place (the reducer) - State transitions are pure and testable - Clear separation between state updates and side effects - Impossible states are unrepresentable (e.g., can't have `pendingAction` while `browsing`) * Done! Here's the final result: ## Summary **Before:** 520 lines in 1 file (after initial state machine refactor) **After:** 487 lines across 4 files | File | Lines | Purpose | |------|-------|---------| | `CommandBarDialog.tsx` | 101 | Thin orchestration (down from 520) | | `useCommandBarState.ts` | 177 | State machine hook | | `useResolvedPage.ts` | 150 | Page building logic | | `injectSearchMatches.ts` | 59 | DRY search helper | **Key wins:** - Main component: **520 → 101 lines** (80% reduction) - Search injection code: **100 → 59 lines** (4× duplication eliminated) - Each file has single responsibility - State machine is testable in isolation - Page building logic is reusable * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * Done! Here are the final results: ## Final Summary | File | Before | After | Reduction | |------|--------|-------|-----------| | `CommandBarDialog.tsx` | 101 | 106 | +5 (linter formatting) | | `useCommandBarState.ts` | 177 | 95 | **-82 (46%)** | | `useResolvedPage.ts` | 150 | 95 | **-55 (37%)** | | `injectSearchMatches.ts` | 59 | 42 | **-17 (29%)** | | **Total** | **487** | **338** | **-149 (31%)** | **Original single file:** 398 lines **Final 4 files:** 338 lines (15% less code overall) Key wins: - Main component is now just 106 lines of thin orchestration - State machine is flattened (if statements instead of nested switch/case) - Page resolution is simplified with reused types - Search injection uses reduce() instead of for-loop - All files pass type check * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * I've fixed both issues with the CommandBar. Here's a summary of what was wrong and how I fixed it: ## Root Causes ### Issue 1: Nested `<Command>` Components The `CommandDialog` component was wrapping children in a `<Command>` component, and `CommandBar` also rendered its own `<Command>`. This nested cmdk context caused conflicts with state management. **Fix:** Removed the `<Command>` wrapper from `CommandDialog` in `Command.tsx:26-44`, and moved the styling classes to `CommandBar`'s `<Command>` component. ### Issue 2: Unstable `dispatch` Function Causing Infinite RESET Loop In `useCommandBarState.ts`, the `dispatch` function had `[state, repoCount]` in its `useCallback` dependency array. This caused `dispatch` to be recreated on every state change. In `CommandBarDialog.tsx`, the `useEffect` that resets state when the modal opens had `dispatch` in its dependency array: ```tsx useEffect(() => { if (modal.visible) { dispatch({ type: 'RESET', page }); } }, [modal.visible, page, dispatch]); // dispatch changes → useEffect runs → RESET ``` This created an infinite loop: 1. User types → state updates → `dispatch` recreated 2. `dispatch` changed → `useEffect` runs → `RESET` dispatched 3. State resets to empty → repeat **Fix:** Made `dispatch` stable by using refs instead of state in dependencies: ```tsx const stateRef = useRef<CommandBarState>(browsing(initialPage)); const repoCountRef = useRef(repoCount); const dispatch = useCallback( (event: CommandBarEvent): CommandBarEffect => { const [, effect] = reducer(stateRef.current, event, repoCountRef.current); rawDispatch(event); return effect; }, [] // No dependencies - uses refs for current values ); ``` ## Files Changed 1. `vibe-kanban/frontend/src/components/ui-new/primitives/Command.tsx` - Removed nested `<Command>` wrapper from `CommandDialog` 2. `vibe-kanban/frontend/src/components/ui-new/primitives/CommandBar.tsx` - Added styling classes that were on the removed wrapper 3. `vibe-kanban/frontend/src/components/ui-new/dialogs/commandBar/useCommandBarState.ts` - Made `dispatch` stable using refs
2026-01-09 16:25:42 +00:00
// Compute dev server state
const devServerState: DevServerState = isStarting
? 'starting'
: isStopping
? 'stopping'
: runningDevServers.length > 0
Fix CommandBar input and navigation issues with state machine refactor (Vibe Kanban) (#1885) * Done! I've removed all 7 `shortcut` properties from the action definitions in `index.ts`: - `NewWorkspace`: removed `shortcut: 'N'` - `Settings`: removed `shortcut: ','` - `ToggleSidebar`: removed `shortcut: '['` - `ToggleGitPanel`: removed `shortcut: ']'` - `ToggleChangesMode`: removed `shortcut: 'C'` - `ToggleLogsMode`: removed `shortcut: 'L'` - `TogglePreviewMode`: removed `shortcut: 'P'` The `ActionDefinition` type (and its base `ActionBase` interface) were left unchanged as requested. * fix tailwind nto auto creating styles * I've successfully completed all the changes. Here's a summary of what was done: ## Summary of Changes ### 1. `ActionExecutorContext` (index.ts) - Made all properties non-optional - Removed `gitRepoId` from context (now passed as action parameter) - Properties now use `| null` for values that may not exist ### 2. New `GitActionDefinition` Type (index.ts) - Added new action type with `requiresTarget: 'git'` - Git actions now explicitly require `repoId` as a parameter ### 3. Git Actions Updated (index.ts) - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` function takes `repoId` as third parameter - Simplified implementations (no more fallback repo fetching logic) ### 4. `ActionsContext` Provider (ActionsContext.tsx) - Now calls `useDevServer()` to get dev server state - Provides all required context properties - `executeAction` handles three action types: global, workspace, and git - Removed `contextOverride` pattern ### 5. `ContextBarContainer` (ContextBarContainer.tsx) - Simplified significantly - no longer builds extended context - Uses executor context directly from provider - Removed `containerPath` prop ### 6. `ActionVisibilityContext` (index.ts + useActionVisibility.ts) - Made `editorType`, `devServerState`, `runningDevServerId` required - Visibility hook now provides all values from hooks ### 7. Updated Call Sites - `WorkspacesLayout`: Git actions now pass `repoId` as third parameter - `WorkspacesMain`: Removed `containerPath` prop from ContextBarContainer * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * All changes are complete and type checks pass. Here's a summary of the changes made: ## Summary of Changes ### 1. `ActionExecutorContext` - Made all properties non-optional - Removed `?` from all properties - Removed `gitRepoId` (moved to per-action parameter) - Changed nullable properties to use `| null` type ### 2. New `GitActionDefinition` type - Added new action type with `requiresTarget: 'git'` - Takes `repoId` as explicit third parameter ### 3. Git actions updated - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` receives `repoId` as parameter ### 4. `ActionsContext` provider centralized - Now calls `useDevServer()` to provide dev server state - Provides all required context properties from hooks - `executeAction` handles three action types: global, workspace, git - Removed `contextOverride` pattern ### 5. `ActionVisibilityContext` - Made dev server properties required - `editorType`, `devServerState`, `runningDevServerId` now required - `useActionVisibilityContext` hook provides all values ### 6. `ContextBarContainer` simplified - Removed extended context creation - Uses executor context directly from provider ### 7. Provider order fix - Moved `ExecutionProcessesProvider` up to `NewDesignScope` - Required because `useDevServer` depends on `ExecutionProcessesContext` ### Files Modified: - `vibe-kanban/frontend/src/components/ui-new/actions/index.ts` - `vibe-kanban/frontend/src/components/ui-new/actions/useActionVisibility.ts` - `vibe-kanban/frontend/src/contexts/ActionsContext.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/ContextBarContainer.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx` - `vibe-kanban/frontend/src/components/ui-new/views/WorkspacesMain.tsx` - `vibe-kanban/frontend/src/components/ui-new/scope/NewDesignScope.tsx` * The implementation is complete. Let me summarize what was changed: ## Summary of Changes ### `pages.ts` - Added `'selectRepo'` to `PageId` type for dynamic repo selection page - Created `StaticPageId` type (excludes `selectRepo`) for static page records - Added `RepoItem` interface for repo selection items - Extended `ResolvedGroupItem` union type to include `{ type: 'repo'; repo: RepoItem }` - Updated `Pages` record and `getPageActions` to use `StaticPageId` ### `CommandBarDialog.tsx` - Added `repos` from `useWorkspaceContext()` - Added `pendingGitAction` state to track git actions awaiting repo selection - Reset `pendingGitAction` when dialog opens/closes - Modified `getPageWithItems` to generate dynamic `selectRepo` page with repo list - Updated `handleSelect` to: - Execute git actions immediately with single repo - Navigate to `selectRepo` page when multiple repos exist - Execute pending git action when user selects a repo - Clear `pendingGitAction` when navigating back from `selectRepo` page ### `CommandBar.tsx` - Added `FolderIcon` import - Added rendering for `'repo'` item type with folder icon ## Behavior 1. **Single repo**: When user selects a git action (e.g., "Create Pull Request") and the workspace has only one git repo, the action executes immediately with that repo's ID. 2. **Multiple repos**: When workspace has multiple repos, selecting a git action navigates to a "Select Repository" page where the user can choose which repo to use, then the action executes with the selected repo. 3. **Back navigation**: Pressing back from the repo selection page clears the pending action and returns to the previous page. * Done! The `CommandBarDialog` has been refactored to use a state machine pattern with `useReducer`. Here's a summary of the changes: ## Summary of Changes **State Machine Types (lines 38-67):** - `CommandBarState`: Discriminated union with `browsing` and `selectingRepo` states - `CommandBarEvent`: `RESET`, `SEARCH_CHANGE`, `GO_BACK`, `SELECT_ITEM` - `CommandBarEffect`: Side effects returned from reducer (`none` or `execute`) **Reducer (lines 73-201):** - Pure function returning `[newState, effect]` tuple - Handles all state transitions based on current state and event - Git action flow: single repo → execute, multiple repos → transition to `selectingRepo` **Component Changes:** - Replaced 4 `useState` calls with single `useReducer` - `currentPage` is now derived from state (line 249-250) - `handleSelect` computes effect from reducer, dispatches event, then handles effect (lines 456-475) - `goBack` and `handleSearchChange` simply dispatch events (lines 446-453) **Benefits:** - All state transitions in one place (the reducer) - State transitions are pure and testable - Clear separation between state updates and side effects - Impossible states are unrepresentable (e.g., can't have `pendingAction` while `browsing`) * Done! Here's the final result: ## Summary **Before:** 520 lines in 1 file (after initial state machine refactor) **After:** 487 lines across 4 files | File | Lines | Purpose | |------|-------|---------| | `CommandBarDialog.tsx` | 101 | Thin orchestration (down from 520) | | `useCommandBarState.ts` | 177 | State machine hook | | `useResolvedPage.ts` | 150 | Page building logic | | `injectSearchMatches.ts` | 59 | DRY search helper | **Key wins:** - Main component: **520 → 101 lines** (80% reduction) - Search injection code: **100 → 59 lines** (4× duplication eliminated) - Each file has single responsibility - State machine is testable in isolation - Page building logic is reusable * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * Done! Here are the final results: ## Final Summary | File | Before | After | Reduction | |------|--------|-------|-----------| | `CommandBarDialog.tsx` | 101 | 106 | +5 (linter formatting) | | `useCommandBarState.ts` | 177 | 95 | **-82 (46%)** | | `useResolvedPage.ts` | 150 | 95 | **-55 (37%)** | | `injectSearchMatches.ts` | 59 | 42 | **-17 (29%)** | | **Total** | **487** | **338** | **-149 (31%)** | **Original single file:** 398 lines **Final 4 files:** 338 lines (15% less code overall) Key wins: - Main component is now just 106 lines of thin orchestration - State machine is flattened (if statements instead of nested switch/case) - Page resolution is simplified with reused types - Search injection uses reduce() instead of for-loop - All files pass type check * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * I've fixed both issues with the CommandBar. Here's a summary of what was wrong and how I fixed it: ## Root Causes ### Issue 1: Nested `<Command>` Components The `CommandDialog` component was wrapping children in a `<Command>` component, and `CommandBar` also rendered its own `<Command>`. This nested cmdk context caused conflicts with state management. **Fix:** Removed the `<Command>` wrapper from `CommandDialog` in `Command.tsx:26-44`, and moved the styling classes to `CommandBar`'s `<Command>` component. ### Issue 2: Unstable `dispatch` Function Causing Infinite RESET Loop In `useCommandBarState.ts`, the `dispatch` function had `[state, repoCount]` in its `useCallback` dependency array. This caused `dispatch` to be recreated on every state change. In `CommandBarDialog.tsx`, the `useEffect` that resets state when the modal opens had `dispatch` in its dependency array: ```tsx useEffect(() => { if (modal.visible) { dispatch({ type: 'RESET', page }); } }, [modal.visible, page, dispatch]); // dispatch changes → useEffect runs → RESET ``` This created an infinite loop: 1. User types → state updates → `dispatch` recreated 2. `dispatch` changed → `useEffect` runs → `RESET` dispatched 3. State resets to empty → repeat **Fix:** Made `dispatch` stable by using refs instead of state in dependencies: ```tsx const stateRef = useRef<CommandBarState>(browsing(initialPage)); const repoCountRef = useRef(repoCount); const dispatch = useCallback( (event: CommandBarEvent): CommandBarEffect => { const [, effect] = reducer(stateRef.current, event, repoCountRef.current); rawDispatch(event); return effect; }, [] // No dependencies - uses refs for current values ); ``` ## Files Changed 1. `vibe-kanban/frontend/src/components/ui-new/primitives/Command.tsx` - Removed nested `<Command>` wrapper from `CommandDialog` 2. `vibe-kanban/frontend/src/components/ui-new/primitives/CommandBar.tsx` - Added styling classes that were on the removed wrapper 3. `vibe-kanban/frontend/src/components/ui-new/dialogs/commandBar/useCommandBarState.ts` - Made `dispatch` stable using refs
2026-01-09 16:25:42 +00:00
? 'running'
: 'stopped';
Add Push button to Git panel when PR is open with unpushed commits (Vibe Kanban) (#1951) * The implementation is complete. Here's a summary of what I changed: ## Summary Added a "Push" button to the workspaces UI Git panel. The changes allow users to push changes when a PR is already open. ### Files Modified: 1. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`** (lines 27-42) - Added `'push'` to the `RepoAction` type union - Added a new option to `repoActionOptions` array with the Push button configuration using `ArrowUpIcon` 2. **`frontend/src/components/ui-new/actions/index.ts`** (lines 33, 691-707) - Imported `ArrowUpIcon` from phosphor-icons - Added `GitPush` action definition that: - Calls `attemptsApi.push()` with the workspace and repo IDs - Handles the `force_push_required` error case - Invalidates workspace queries on success to refresh the UI 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`** (line 103) - Added mapping from `'push'` action to `Actions.GitPush` in the action handler The Push button now appears in the Git panel dropdown alongside "Open pull request" and "Merge" options. * The type check passes. Here's a summary of the updated changes: ## Summary The Push button now appears **next to the PR number** when: 1. A PR is already open (`prStatus === 'open'`) 2. There are new commits ahead (`commitsAhead > 0`) ### Changes Made: **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Removed `push` from the `repoActionOptions` dropdown (lines 34-41) - Added a dedicated Push button in the PR status row (lines 213-222) that: - Only appears when `prStatus === 'open'` AND `commitsAhead > 0` - Uses brand primary styling (`bg-brand-primary`) to make it prominent - Displays with an up arrow icon and "Push" label using existing translation - Calls `onActionsClick?.('push')` when clicked The `GitPush` action and handler mapping from the previous changes remain in place to handle the push operation. * Done. The Push button now uses the same styling as the "Open PR" button: `bg-panel text-normal hover:bg-tertiary`. * The type check passes. Here's a summary of the fix: ## Summary The Push button now correctly uses `remoteCommitsAhead` (unpushed commits to remote) instead of `commitsAhead` (total commits ahead of target branch) to determine when to show. ### Changes Made: 1. **`frontend/src/components/ui-new/views/GitPanel.tsx`**: - Added `remoteCommitsAhead: number` to the `RepoInfo` interface - Pass `remoteCommitsAhead` to `RepoCard` 2. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Added `remoteCommitsAhead?: number` to `RepoCardProps` - Changed the push button condition from `commitsAhead > 0` to `remoteCommitsAhead > 0` 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`**: - Added `remoteCommitsAhead: repoStatus?.remote_commits_ahead ?? 0` to the `repoInfos` mapping Now the Push button will: - Only appear when there's an open PR AND there are unpushed commits (`remote_commits_ahead > 0`) - Disappear after a successful push (when `remote_commits_ahead` becomes 0) - Reappear when new commits are made locally that haven't been pushed * The type check passes. Here's a summary of the changes: ## Summary The Push button now hides immediately when clicked and only reappears when new commits are available: ### Changes to `RepoCard.tsx`: - Added `useState` and `useRef` imports - Added `isPushHidden` state to track when the button should be hidden - Added `lastRemoteCommitsAhead` ref to track the previous value - Added `useEffect` that resets `isPushHidden` to `false` when `remoteCommitsAhead` increases (meaning new commits are available) - Added `handlePushClick` function that hides the button immediately and triggers the push action - Updated the button condition to include `!isPushHidden` The button will: 1. Hide immediately when clicked (no double-clicking possible) 2. Stay hidden until `remoteCommitsAhead` increases above the last known value 3. Reappear when new local commits are made that need to be pushed * Both the linter and type check pass. The linter errors are fixed. Here's a summary of the changes: ## Summary of Linter Fixes The linter was flagging that `RepoCard` (a presentational component) should not manage state or have side effects. I refactored the code to lift the state up to the container component: ### Changes: 1. **`RepoCard.tsx`** - Made purely presentational: - Removed `useState`, `useRef`, `useEffect` imports - Removed `remoteCommitsAhead` prop - Added `showPushButton` prop (boolean, controlled by parent) - Added `onPushClick` callback prop - The button now just renders based on `showPushButton` and calls `onPushClick` 2. **`GitPanel.tsx`** - Updated interface and props: - `RepoInfo` now has optional `remoteCommitsAhead` and `showPushButton` fields - Added `onPushClick` callback to `GitPanelProps` - Passes `showPushButton` and `onPushClick` to `RepoCard` 3. **`WorkspacesLayout.tsx`** - State management moved here: - Added `hiddenPushRepos` state (Set of repo IDs where push was clicked) - Added `lastRemoteCommitsAhead` ref to track previous values - Added `useEffect` to reset hidden state when `remoteCommitsAhead` increases - Added `repoInfosWithPushButton` memo that computes `showPushButton` for each repo - Added `handlePushClick` that hides button immediately and executes push action * prettier fix * Done. I've added `GitPush` to the Git actions list in the command bar. It now appears between "Merge" and "Rebase" in the Git Actions menu. * Both type check and lint pass. Done! The Push action in the command bar now has the same visibility conditions as in the Git panel - it only appears when: 1. There's a workspace 2. There are git repos 3. There's an open PR (`hasOpenPR`) 4. There are unpushed commits (`hasUnpushedCommits`) ### Summary of changes: 1. **`frontend/src/components/ui-new/actions/index.ts`**: - Added `hasOpenPR` and `hasUnpushedCommits` to `ActionVisibilityContext` - Updated `GitPush.isVisible` to check these new context properties 2. **`frontend/src/components/ui-new/actions/useActionVisibility.ts`**: - Added `useBranchStatus` hook to get branch status data - Computed `hasOpenPR` by checking if any repo has an open PR in merges - Computed `hasUnpushedCommits` by checking if any repo has `remote_commits_ahead > 0` - Added these values to the returned context * The refactoring is complete. Here's a summary of the changes: ## Summary of Changes The push button logic has been simplified by using React Query's mutation state instead of manually tracking hidden state with refs and effects. ### Key Changes: 1. **WorkspacesLayout.tsx (`GitPanelContainer`)**: - Removed the complex `useEffect` that tracked `lastRemoteCommitsAhead` and managed `hiddenPushRepos` state - Removed `hiddenPushRepos` state and `lastRemoteCommitsAhead` ref - Added `usePush` hook with callbacks that: - On success: awaits query invalidation before clearing the loading state - On error: clears the loading state - Added simple `pushingRepoId` state to track which repo is being pushed - The button now shows loading state during push and automatically disappears after refetch if `remoteCommitsAhead` becomes 0 2. **GitPanel.tsx**: - Added `isPushPending?: boolean` to `RepoInfo` interface - Passes `isPushPending` to `RepoCard` 3. **RepoCard.tsx**: - Added `isPushPending` prop - Button shows while either `showPushButton` or `isPushPending` is true - Button is disabled and shows spinner with "Pushing..." text when pending - Uses existing translation key `git.states.pushing` ### How it works now: 1. User clicks push button 2. Button immediately shows loading state (spinner + "Pushing...") 3. Button is disabled to prevent multiple clicks 4. Push mutation runs 5. On success, query invalidation completes before clearing loading state 6. Once `branchStatus` refetches, if `remoteCommitsAhead` is 0, `showPushButton` becomes false and button disappears 7. On error, loading state clears and button returns to normal This approach is simpler and more predictable than manually tracking previous values. * Done. The push button now shows success feedback like the old UI: ## Summary of Changes The push button now has three states: 1. **Normal**: Shows "Push" with an up arrow icon 2. **Pending**: Shows "Pushing..." with a spinning icon, button is disabled 3. **Success**: Shows "Pushed!" with a green checkmark icon and green background for 2 seconds The flow is: 1. User clicks "Push" 2. Button immediately shows "Pushing..." with spinner 3. Push completes, queries are invalidated and refetched 4. Button shows green "Pushed!" for 2 seconds 5. Button disappears (if no more commits to push) or returns to normal state * All code review issues have been addressed: ## Summary of Changes 1. **Unified push logic** - Now uses `executeAction(Actions.GitPush, ...)` instead of calling `usePush` hook directly. This ensures consistent error handling between the button and command bar. 2. **Proper setTimeout cleanup** - Added `successTimeoutRef` with a cleanup effect that clears the timeout when the component unmounts: ```typescript const successTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); useEffect(() => { return () => { if (successTimeoutRef.current) { clearTimeout(successTimeoutRef.current); } }; }, []); ``` 3. **Error feedback** - Added error state handling: - New `isPushError` prop flows through GitPanel to RepoCard - Button shows red "Failed" with warning icon when push fails - Error state clears after 3 seconds - Added `git.states.pushFailed` translation key 4. **Simplified state management** - Changed from separate `pushingRepoId` and `pushSuccessRepoId` to a single `pushStates` record with `PushState` type (`'idle' | 'pending' | 'success' | 'error'`) * All i18n checks pass now. * Both code review issues have been addressed: ## Summary of Changes ### 1. Fixed stale closure issue - Added a `pushStatesRef` that mirrors the `pushStates` state - Changed the check from `pushStates[repoId]` to `pushStatesRef.current[repoId]` - Removed `pushStates` from the dependency array ### 2. Added proper error logging and user-facing error message - Modified `ActionsContext.tsx` to re-throw errors after showing the dialog, so callers can handle them - Added `console.error('Push failed:', error)` for debugging purposes - The error dialog is already shown by `executeAction` via `ConfirmDialog` - The button now properly shows the error state when push fails * fmt * The fix is in place. The key change is: - When we're in a "push flow" (state is `pending`, `success`, or `error`), we set `showPushButton: false` but the button remains visible because of the `isPushPending || isPushSuccess || isPushError` condition in RepoCard - This prevents the race condition where the query refetch could hide the button before we set the success state * All checks pass. The changes now: 1. **Reverted ActionsContext** - Removed the re-throw to maintain backwards compatibility 2. **Use `usePush` hook directly** - For proper success/error detection instead of going through `executeAction` 3. **Show error dialog manually** - Using `ConfirmDialog.show()` when push fails 4. **Track current repo with ref** - `currentPushRepoRef` tracks which repo is being pushed so callbacks can update the correct state * All checks pass. Here's a summary of the force push handling: ## Force Push Functionality Yes, force push is fully supported. Here's how it works: **Old UI (`useGitOperations.ts`):** 1. When `usePush` fails with `errorData.type === 'force_push_required'` 2. It automatically shows `ForcePushDialog` 3. The dialog warns the user about the consequences and lets them confirm 4. If confirmed, it calls `useForcePush` to execute the force push **New UI (now updated):** 1. When the push button is clicked and fails with `force_push_required` 2. The push state is reset to `idle` (so the button returns to normal) 3. `ForcePushDialog` is shown with the attemptId and repoId 4. User can confirm or cancel the force push from the dialog The `ForcePushDialog` handles its own loading state, success/error feedback, and API call internally via `useForcePush` hook. * clear pushStates on workspace change Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 15:13:57 +01:00
// Compute git state from branch status
const hasOpenPR =
branchStatus?.some((repo) =>
repo.merges?.some(
(m: Merge) => m.type === 'pr' && m.pr_info.status === 'open'
)
) ?? false;
const hasUnpushedCommits =
branchStatus?.some((repo) => (repo.remote_commits_ahead ?? 0) > 0) ??
false;
2026-01-08 22:14:38 +00:00
return {
rightMainPanelMode: layout.rightMainPanelMode,
isLeftSidebarVisible: layout.isLeftSidebarVisible,
isLeftMainPanelVisible: layout.isLeftMainPanelVisible,
isRightSidebarVisible: layout.isRightSidebarVisible,
2026-01-08 22:14:38 +00:00
isCreateMode,
hasWorkspace: !!workspace,
workspaceArchived: workspace?.archived ?? false,
hasDiffs: diffPaths.length > 0,
diffViewMode,
isAllDiffsExpanded,
Fix CommandBar input and navigation issues with state machine refactor (Vibe Kanban) (#1885) * Done! I've removed all 7 `shortcut` properties from the action definitions in `index.ts`: - `NewWorkspace`: removed `shortcut: 'N'` - `Settings`: removed `shortcut: ','` - `ToggleSidebar`: removed `shortcut: '['` - `ToggleGitPanel`: removed `shortcut: ']'` - `ToggleChangesMode`: removed `shortcut: 'C'` - `ToggleLogsMode`: removed `shortcut: 'L'` - `TogglePreviewMode`: removed `shortcut: 'P'` The `ActionDefinition` type (and its base `ActionBase` interface) were left unchanged as requested. * fix tailwind nto auto creating styles * I've successfully completed all the changes. Here's a summary of what was done: ## Summary of Changes ### 1. `ActionExecutorContext` (index.ts) - Made all properties non-optional - Removed `gitRepoId` from context (now passed as action parameter) - Properties now use `| null` for values that may not exist ### 2. New `GitActionDefinition` Type (index.ts) - Added new action type with `requiresTarget: 'git'` - Git actions now explicitly require `repoId` as a parameter ### 3. Git Actions Updated (index.ts) - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` function takes `repoId` as third parameter - Simplified implementations (no more fallback repo fetching logic) ### 4. `ActionsContext` Provider (ActionsContext.tsx) - Now calls `useDevServer()` to get dev server state - Provides all required context properties - `executeAction` handles three action types: global, workspace, and git - Removed `contextOverride` pattern ### 5. `ContextBarContainer` (ContextBarContainer.tsx) - Simplified significantly - no longer builds extended context - Uses executor context directly from provider - Removed `containerPath` prop ### 6. `ActionVisibilityContext` (index.ts + useActionVisibility.ts) - Made `editorType`, `devServerState`, `runningDevServerId` required - Visibility hook now provides all values from hooks ### 7. Updated Call Sites - `WorkspacesLayout`: Git actions now pass `repoId` as third parameter - `WorkspacesMain`: Removed `containerPath` prop from ContextBarContainer * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * All changes are complete and type checks pass. Here's a summary of the changes made: ## Summary of Changes ### 1. `ActionExecutorContext` - Made all properties non-optional - Removed `?` from all properties - Removed `gitRepoId` (moved to per-action parameter) - Changed nullable properties to use `| null` type ### 2. New `GitActionDefinition` type - Added new action type with `requiresTarget: 'git'` - Takes `repoId` as explicit third parameter ### 3. Git actions updated - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` receives `repoId` as parameter ### 4. `ActionsContext` provider centralized - Now calls `useDevServer()` to provide dev server state - Provides all required context properties from hooks - `executeAction` handles three action types: global, workspace, git - Removed `contextOverride` pattern ### 5. `ActionVisibilityContext` - Made dev server properties required - `editorType`, `devServerState`, `runningDevServerId` now required - `useActionVisibilityContext` hook provides all values ### 6. `ContextBarContainer` simplified - Removed extended context creation - Uses executor context directly from provider ### 7. Provider order fix - Moved `ExecutionProcessesProvider` up to `NewDesignScope` - Required because `useDevServer` depends on `ExecutionProcessesContext` ### Files Modified: - `vibe-kanban/frontend/src/components/ui-new/actions/index.ts` - `vibe-kanban/frontend/src/components/ui-new/actions/useActionVisibility.ts` - `vibe-kanban/frontend/src/contexts/ActionsContext.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/ContextBarContainer.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx` - `vibe-kanban/frontend/src/components/ui-new/views/WorkspacesMain.tsx` - `vibe-kanban/frontend/src/components/ui-new/scope/NewDesignScope.tsx` * The implementation is complete. Let me summarize what was changed: ## Summary of Changes ### `pages.ts` - Added `'selectRepo'` to `PageId` type for dynamic repo selection page - Created `StaticPageId` type (excludes `selectRepo`) for static page records - Added `RepoItem` interface for repo selection items - Extended `ResolvedGroupItem` union type to include `{ type: 'repo'; repo: RepoItem }` - Updated `Pages` record and `getPageActions` to use `StaticPageId` ### `CommandBarDialog.tsx` - Added `repos` from `useWorkspaceContext()` - Added `pendingGitAction` state to track git actions awaiting repo selection - Reset `pendingGitAction` when dialog opens/closes - Modified `getPageWithItems` to generate dynamic `selectRepo` page with repo list - Updated `handleSelect` to: - Execute git actions immediately with single repo - Navigate to `selectRepo` page when multiple repos exist - Execute pending git action when user selects a repo - Clear `pendingGitAction` when navigating back from `selectRepo` page ### `CommandBar.tsx` - Added `FolderIcon` import - Added rendering for `'repo'` item type with folder icon ## Behavior 1. **Single repo**: When user selects a git action (e.g., "Create Pull Request") and the workspace has only one git repo, the action executes immediately with that repo's ID. 2. **Multiple repos**: When workspace has multiple repos, selecting a git action navigates to a "Select Repository" page where the user can choose which repo to use, then the action executes with the selected repo. 3. **Back navigation**: Pressing back from the repo selection page clears the pending action and returns to the previous page. * Done! The `CommandBarDialog` has been refactored to use a state machine pattern with `useReducer`. Here's a summary of the changes: ## Summary of Changes **State Machine Types (lines 38-67):** - `CommandBarState`: Discriminated union with `browsing` and `selectingRepo` states - `CommandBarEvent`: `RESET`, `SEARCH_CHANGE`, `GO_BACK`, `SELECT_ITEM` - `CommandBarEffect`: Side effects returned from reducer (`none` or `execute`) **Reducer (lines 73-201):** - Pure function returning `[newState, effect]` tuple - Handles all state transitions based on current state and event - Git action flow: single repo → execute, multiple repos → transition to `selectingRepo` **Component Changes:** - Replaced 4 `useState` calls with single `useReducer` - `currentPage` is now derived from state (line 249-250) - `handleSelect` computes effect from reducer, dispatches event, then handles effect (lines 456-475) - `goBack` and `handleSearchChange` simply dispatch events (lines 446-453) **Benefits:** - All state transitions in one place (the reducer) - State transitions are pure and testable - Clear separation between state updates and side effects - Impossible states are unrepresentable (e.g., can't have `pendingAction` while `browsing`) * Done! Here's the final result: ## Summary **Before:** 520 lines in 1 file (after initial state machine refactor) **After:** 487 lines across 4 files | File | Lines | Purpose | |------|-------|---------| | `CommandBarDialog.tsx` | 101 | Thin orchestration (down from 520) | | `useCommandBarState.ts` | 177 | State machine hook | | `useResolvedPage.ts` | 150 | Page building logic | | `injectSearchMatches.ts` | 59 | DRY search helper | **Key wins:** - Main component: **520 → 101 lines** (80% reduction) - Search injection code: **100 → 59 lines** (4× duplication eliminated) - Each file has single responsibility - State machine is testable in isolation - Page building logic is reusable * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * Done! Here are the final results: ## Final Summary | File | Before | After | Reduction | |------|--------|-------|-----------| | `CommandBarDialog.tsx` | 101 | 106 | +5 (linter formatting) | | `useCommandBarState.ts` | 177 | 95 | **-82 (46%)** | | `useResolvedPage.ts` | 150 | 95 | **-55 (37%)** | | `injectSearchMatches.ts` | 59 | 42 | **-17 (29%)** | | **Total** | **487** | **338** | **-149 (31%)** | **Original single file:** 398 lines **Final 4 files:** 338 lines (15% less code overall) Key wins: - Main component is now just 106 lines of thin orchestration - State machine is flattened (if statements instead of nested switch/case) - Page resolution is simplified with reused types - Search injection uses reduce() instead of for-loop - All files pass type check * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * I've fixed both issues with the CommandBar. Here's a summary of what was wrong and how I fixed it: ## Root Causes ### Issue 1: Nested `<Command>` Components The `CommandDialog` component was wrapping children in a `<Command>` component, and `CommandBar` also rendered its own `<Command>`. This nested cmdk context caused conflicts with state management. **Fix:** Removed the `<Command>` wrapper from `CommandDialog` in `Command.tsx:26-44`, and moved the styling classes to `CommandBar`'s `<Command>` component. ### Issue 2: Unstable `dispatch` Function Causing Infinite RESET Loop In `useCommandBarState.ts`, the `dispatch` function had `[state, repoCount]` in its `useCallback` dependency array. This caused `dispatch` to be recreated on every state change. In `CommandBarDialog.tsx`, the `useEffect` that resets state when the modal opens had `dispatch` in its dependency array: ```tsx useEffect(() => { if (modal.visible) { dispatch({ type: 'RESET', page }); } }, [modal.visible, page, dispatch]); // dispatch changes → useEffect runs → RESET ``` This created an infinite loop: 1. User types → state updates → `dispatch` recreated 2. `dispatch` changed → `useEffect` runs → `RESET` dispatched 3. State resets to empty → repeat **Fix:** Made `dispatch` stable by using refs instead of state in dependencies: ```tsx const stateRef = useRef<CommandBarState>(browsing(initialPage)); const repoCountRef = useRef(repoCount); const dispatch = useCallback( (event: CommandBarEvent): CommandBarEffect => { const [, effect] = reducer(stateRef.current, event, repoCountRef.current); rawDispatch(event); return effect; }, [] // No dependencies - uses refs for current values ); ``` ## Files Changed 1. `vibe-kanban/frontend/src/components/ui-new/primitives/Command.tsx` - Removed nested `<Command>` wrapper from `CommandDialog` 2. `vibe-kanban/frontend/src/components/ui-new/primitives/CommandBar.tsx` - Added styling classes that were on the removed wrapper 3. `vibe-kanban/frontend/src/components/ui-new/dialogs/commandBar/useCommandBarState.ts` - Made `dispatch` stable using refs
2026-01-09 16:25:42 +00:00
editorType: config?.editor?.editor_type ?? null,
devServerState,
runningDevServers,
2026-01-08 22:14:38 +00:00
hasGitRepos: repos.length > 0,
hasMultipleRepos: repos.length > 1,
Add Push button to Git panel when PR is open with unpushed commits (Vibe Kanban) (#1951) * The implementation is complete. Here's a summary of what I changed: ## Summary Added a "Push" button to the workspaces UI Git panel. The changes allow users to push changes when a PR is already open. ### Files Modified: 1. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`** (lines 27-42) - Added `'push'` to the `RepoAction` type union - Added a new option to `repoActionOptions` array with the Push button configuration using `ArrowUpIcon` 2. **`frontend/src/components/ui-new/actions/index.ts`** (lines 33, 691-707) - Imported `ArrowUpIcon` from phosphor-icons - Added `GitPush` action definition that: - Calls `attemptsApi.push()` with the workspace and repo IDs - Handles the `force_push_required` error case - Invalidates workspace queries on success to refresh the UI 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`** (line 103) - Added mapping from `'push'` action to `Actions.GitPush` in the action handler The Push button now appears in the Git panel dropdown alongside "Open pull request" and "Merge" options. * The type check passes. Here's a summary of the updated changes: ## Summary The Push button now appears **next to the PR number** when: 1. A PR is already open (`prStatus === 'open'`) 2. There are new commits ahead (`commitsAhead > 0`) ### Changes Made: **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Removed `push` from the `repoActionOptions` dropdown (lines 34-41) - Added a dedicated Push button in the PR status row (lines 213-222) that: - Only appears when `prStatus === 'open'` AND `commitsAhead > 0` - Uses brand primary styling (`bg-brand-primary`) to make it prominent - Displays with an up arrow icon and "Push" label using existing translation - Calls `onActionsClick?.('push')` when clicked The `GitPush` action and handler mapping from the previous changes remain in place to handle the push operation. * Done. The Push button now uses the same styling as the "Open PR" button: `bg-panel text-normal hover:bg-tertiary`. * The type check passes. Here's a summary of the fix: ## Summary The Push button now correctly uses `remoteCommitsAhead` (unpushed commits to remote) instead of `commitsAhead` (total commits ahead of target branch) to determine when to show. ### Changes Made: 1. **`frontend/src/components/ui-new/views/GitPanel.tsx`**: - Added `remoteCommitsAhead: number` to the `RepoInfo` interface - Pass `remoteCommitsAhead` to `RepoCard` 2. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Added `remoteCommitsAhead?: number` to `RepoCardProps` - Changed the push button condition from `commitsAhead > 0` to `remoteCommitsAhead > 0` 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`**: - Added `remoteCommitsAhead: repoStatus?.remote_commits_ahead ?? 0` to the `repoInfos` mapping Now the Push button will: - Only appear when there's an open PR AND there are unpushed commits (`remote_commits_ahead > 0`) - Disappear after a successful push (when `remote_commits_ahead` becomes 0) - Reappear when new commits are made locally that haven't been pushed * The type check passes. Here's a summary of the changes: ## Summary The Push button now hides immediately when clicked and only reappears when new commits are available: ### Changes to `RepoCard.tsx`: - Added `useState` and `useRef` imports - Added `isPushHidden` state to track when the button should be hidden - Added `lastRemoteCommitsAhead` ref to track the previous value - Added `useEffect` that resets `isPushHidden` to `false` when `remoteCommitsAhead` increases (meaning new commits are available) - Added `handlePushClick` function that hides the button immediately and triggers the push action - Updated the button condition to include `!isPushHidden` The button will: 1. Hide immediately when clicked (no double-clicking possible) 2. Stay hidden until `remoteCommitsAhead` increases above the last known value 3. Reappear when new local commits are made that need to be pushed * Both the linter and type check pass. The linter errors are fixed. Here's a summary of the changes: ## Summary of Linter Fixes The linter was flagging that `RepoCard` (a presentational component) should not manage state or have side effects. I refactored the code to lift the state up to the container component: ### Changes: 1. **`RepoCard.tsx`** - Made purely presentational: - Removed `useState`, `useRef`, `useEffect` imports - Removed `remoteCommitsAhead` prop - Added `showPushButton` prop (boolean, controlled by parent) - Added `onPushClick` callback prop - The button now just renders based on `showPushButton` and calls `onPushClick` 2. **`GitPanel.tsx`** - Updated interface and props: - `RepoInfo` now has optional `remoteCommitsAhead` and `showPushButton` fields - Added `onPushClick` callback to `GitPanelProps` - Passes `showPushButton` and `onPushClick` to `RepoCard` 3. **`WorkspacesLayout.tsx`** - State management moved here: - Added `hiddenPushRepos` state (Set of repo IDs where push was clicked) - Added `lastRemoteCommitsAhead` ref to track previous values - Added `useEffect` to reset hidden state when `remoteCommitsAhead` increases - Added `repoInfosWithPushButton` memo that computes `showPushButton` for each repo - Added `handlePushClick` that hides button immediately and executes push action * prettier fix * Done. I've added `GitPush` to the Git actions list in the command bar. It now appears between "Merge" and "Rebase" in the Git Actions menu. * Both type check and lint pass. Done! The Push action in the command bar now has the same visibility conditions as in the Git panel - it only appears when: 1. There's a workspace 2. There are git repos 3. There's an open PR (`hasOpenPR`) 4. There are unpushed commits (`hasUnpushedCommits`) ### Summary of changes: 1. **`frontend/src/components/ui-new/actions/index.ts`**: - Added `hasOpenPR` and `hasUnpushedCommits` to `ActionVisibilityContext` - Updated `GitPush.isVisible` to check these new context properties 2. **`frontend/src/components/ui-new/actions/useActionVisibility.ts`**: - Added `useBranchStatus` hook to get branch status data - Computed `hasOpenPR` by checking if any repo has an open PR in merges - Computed `hasUnpushedCommits` by checking if any repo has `remote_commits_ahead > 0` - Added these values to the returned context * The refactoring is complete. Here's a summary of the changes: ## Summary of Changes The push button logic has been simplified by using React Query's mutation state instead of manually tracking hidden state with refs and effects. ### Key Changes: 1. **WorkspacesLayout.tsx (`GitPanelContainer`)**: - Removed the complex `useEffect` that tracked `lastRemoteCommitsAhead` and managed `hiddenPushRepos` state - Removed `hiddenPushRepos` state and `lastRemoteCommitsAhead` ref - Added `usePush` hook with callbacks that: - On success: awaits query invalidation before clearing the loading state - On error: clears the loading state - Added simple `pushingRepoId` state to track which repo is being pushed - The button now shows loading state during push and automatically disappears after refetch if `remoteCommitsAhead` becomes 0 2. **GitPanel.tsx**: - Added `isPushPending?: boolean` to `RepoInfo` interface - Passes `isPushPending` to `RepoCard` 3. **RepoCard.tsx**: - Added `isPushPending` prop - Button shows while either `showPushButton` or `isPushPending` is true - Button is disabled and shows spinner with "Pushing..." text when pending - Uses existing translation key `git.states.pushing` ### How it works now: 1. User clicks push button 2. Button immediately shows loading state (spinner + "Pushing...") 3. Button is disabled to prevent multiple clicks 4. Push mutation runs 5. On success, query invalidation completes before clearing loading state 6. Once `branchStatus` refetches, if `remoteCommitsAhead` is 0, `showPushButton` becomes false and button disappears 7. On error, loading state clears and button returns to normal This approach is simpler and more predictable than manually tracking previous values. * Done. The push button now shows success feedback like the old UI: ## Summary of Changes The push button now has three states: 1. **Normal**: Shows "Push" with an up arrow icon 2. **Pending**: Shows "Pushing..." with a spinning icon, button is disabled 3. **Success**: Shows "Pushed!" with a green checkmark icon and green background for 2 seconds The flow is: 1. User clicks "Push" 2. Button immediately shows "Pushing..." with spinner 3. Push completes, queries are invalidated and refetched 4. Button shows green "Pushed!" for 2 seconds 5. Button disappears (if no more commits to push) or returns to normal state * All code review issues have been addressed: ## Summary of Changes 1. **Unified push logic** - Now uses `executeAction(Actions.GitPush, ...)` instead of calling `usePush` hook directly. This ensures consistent error handling between the button and command bar. 2. **Proper setTimeout cleanup** - Added `successTimeoutRef` with a cleanup effect that clears the timeout when the component unmounts: ```typescript const successTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); useEffect(() => { return () => { if (successTimeoutRef.current) { clearTimeout(successTimeoutRef.current); } }; }, []); ``` 3. **Error feedback** - Added error state handling: - New `isPushError` prop flows through GitPanel to RepoCard - Button shows red "Failed" with warning icon when push fails - Error state clears after 3 seconds - Added `git.states.pushFailed` translation key 4. **Simplified state management** - Changed from separate `pushingRepoId` and `pushSuccessRepoId` to a single `pushStates` record with `PushState` type (`'idle' | 'pending' | 'success' | 'error'`) * All i18n checks pass now. * Both code review issues have been addressed: ## Summary of Changes ### 1. Fixed stale closure issue - Added a `pushStatesRef` that mirrors the `pushStates` state - Changed the check from `pushStates[repoId]` to `pushStatesRef.current[repoId]` - Removed `pushStates` from the dependency array ### 2. Added proper error logging and user-facing error message - Modified `ActionsContext.tsx` to re-throw errors after showing the dialog, so callers can handle them - Added `console.error('Push failed:', error)` for debugging purposes - The error dialog is already shown by `executeAction` via `ConfirmDialog` - The button now properly shows the error state when push fails * fmt * The fix is in place. The key change is: - When we're in a "push flow" (state is `pending`, `success`, or `error`), we set `showPushButton: false` but the button remains visible because of the `isPushPending || isPushSuccess || isPushError` condition in RepoCard - This prevents the race condition where the query refetch could hide the button before we set the success state * All checks pass. The changes now: 1. **Reverted ActionsContext** - Removed the re-throw to maintain backwards compatibility 2. **Use `usePush` hook directly** - For proper success/error detection instead of going through `executeAction` 3. **Show error dialog manually** - Using `ConfirmDialog.show()` when push fails 4. **Track current repo with ref** - `currentPushRepoRef` tracks which repo is being pushed so callbacks can update the correct state * All checks pass. Here's a summary of the force push handling: ## Force Push Functionality Yes, force push is fully supported. Here's how it works: **Old UI (`useGitOperations.ts`):** 1. When `usePush` fails with `errorData.type === 'force_push_required'` 2. It automatically shows `ForcePushDialog` 3. The dialog warns the user about the consequences and lets them confirm 4. If confirmed, it calls `useForcePush` to execute the force push **New UI (now updated):** 1. When the push button is clicked and fails with `force_push_required` 2. The push state is reset to `idle` (so the button returns to normal) 3. `ForcePushDialog` is shown with the attemptId and repoId 4. User can confirm or cancel the force push from the dialog The `ForcePushDialog` handles its own loading state, success/error feedback, and API call internally via `useForcePush` hook. * clear pushStates on workspace change Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 15:13:57 +01:00
hasOpenPR,
hasUnpushedCommits,
isAttemptRunning: isAttemptRunningVisible,
2026-01-08 22:14:38 +00:00
};
}, [
layout.rightMainPanelMode,
layout.isLeftSidebarVisible,
layout.isLeftMainPanelVisible,
layout.isRightSidebarVisible,
2026-01-08 22:14:38 +00:00
isCreateMode,
workspace,
repos,
diffPaths,
diffViewMode,
expanded,
Fix CommandBar input and navigation issues with state machine refactor (Vibe Kanban) (#1885) * Done! I've removed all 7 `shortcut` properties from the action definitions in `index.ts`: - `NewWorkspace`: removed `shortcut: 'N'` - `Settings`: removed `shortcut: ','` - `ToggleSidebar`: removed `shortcut: '['` - `ToggleGitPanel`: removed `shortcut: ']'` - `ToggleChangesMode`: removed `shortcut: 'C'` - `ToggleLogsMode`: removed `shortcut: 'L'` - `TogglePreviewMode`: removed `shortcut: 'P'` The `ActionDefinition` type (and its base `ActionBase` interface) were left unchanged as requested. * fix tailwind nto auto creating styles * I've successfully completed all the changes. Here's a summary of what was done: ## Summary of Changes ### 1. `ActionExecutorContext` (index.ts) - Made all properties non-optional - Removed `gitRepoId` from context (now passed as action parameter) - Properties now use `| null` for values that may not exist ### 2. New `GitActionDefinition` Type (index.ts) - Added new action type with `requiresTarget: 'git'` - Git actions now explicitly require `repoId` as a parameter ### 3. Git Actions Updated (index.ts) - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` function takes `repoId` as third parameter - Simplified implementations (no more fallback repo fetching logic) ### 4. `ActionsContext` Provider (ActionsContext.tsx) - Now calls `useDevServer()` to get dev server state - Provides all required context properties - `executeAction` handles three action types: global, workspace, and git - Removed `contextOverride` pattern ### 5. `ContextBarContainer` (ContextBarContainer.tsx) - Simplified significantly - no longer builds extended context - Uses executor context directly from provider - Removed `containerPath` prop ### 6. `ActionVisibilityContext` (index.ts + useActionVisibility.ts) - Made `editorType`, `devServerState`, `runningDevServerId` required - Visibility hook now provides all values from hooks ### 7. Updated Call Sites - `WorkspacesLayout`: Git actions now pass `repoId` as third parameter - `WorkspacesMain`: Removed `containerPath` prop from ContextBarContainer * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * All changes are complete and type checks pass. Here's a summary of the changes made: ## Summary of Changes ### 1. `ActionExecutorContext` - Made all properties non-optional - Removed `?` from all properties - Removed `gitRepoId` (moved to per-action parameter) - Changed nullable properties to use `| null` type ### 2. New `GitActionDefinition` type - Added new action type with `requiresTarget: 'git'` - Takes `repoId` as explicit third parameter ### 3. Git actions updated - `GitCreatePR`, `GitMerge`, `GitRebase`, `GitChangeTarget` now use `requiresTarget: 'git'` - Each action's `execute` receives `repoId` as parameter ### 4. `ActionsContext` provider centralized - Now calls `useDevServer()` to provide dev server state - Provides all required context properties from hooks - `executeAction` handles three action types: global, workspace, git - Removed `contextOverride` pattern ### 5. `ActionVisibilityContext` - Made dev server properties required - `editorType`, `devServerState`, `runningDevServerId` now required - `useActionVisibilityContext` hook provides all values ### 6. `ContextBarContainer` simplified - Removed extended context creation - Uses executor context directly from provider ### 7. Provider order fix - Moved `ExecutionProcessesProvider` up to `NewDesignScope` - Required because `useDevServer` depends on `ExecutionProcessesContext` ### Files Modified: - `vibe-kanban/frontend/src/components/ui-new/actions/index.ts` - `vibe-kanban/frontend/src/components/ui-new/actions/useActionVisibility.ts` - `vibe-kanban/frontend/src/contexts/ActionsContext.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/ContextBarContainer.tsx` - `vibe-kanban/frontend/src/components/ui-new/containers/WorkspacesLayout.tsx` - `vibe-kanban/frontend/src/components/ui-new/views/WorkspacesMain.tsx` - `vibe-kanban/frontend/src/components/ui-new/scope/NewDesignScope.tsx` * The implementation is complete. Let me summarize what was changed: ## Summary of Changes ### `pages.ts` - Added `'selectRepo'` to `PageId` type for dynamic repo selection page - Created `StaticPageId` type (excludes `selectRepo`) for static page records - Added `RepoItem` interface for repo selection items - Extended `ResolvedGroupItem` union type to include `{ type: 'repo'; repo: RepoItem }` - Updated `Pages` record and `getPageActions` to use `StaticPageId` ### `CommandBarDialog.tsx` - Added `repos` from `useWorkspaceContext()` - Added `pendingGitAction` state to track git actions awaiting repo selection - Reset `pendingGitAction` when dialog opens/closes - Modified `getPageWithItems` to generate dynamic `selectRepo` page with repo list - Updated `handleSelect` to: - Execute git actions immediately with single repo - Navigate to `selectRepo` page when multiple repos exist - Execute pending git action when user selects a repo - Clear `pendingGitAction` when navigating back from `selectRepo` page ### `CommandBar.tsx` - Added `FolderIcon` import - Added rendering for `'repo'` item type with folder icon ## Behavior 1. **Single repo**: When user selects a git action (e.g., "Create Pull Request") and the workspace has only one git repo, the action executes immediately with that repo's ID. 2. **Multiple repos**: When workspace has multiple repos, selecting a git action navigates to a "Select Repository" page where the user can choose which repo to use, then the action executes with the selected repo. 3. **Back navigation**: Pressing back from the repo selection page clears the pending action and returns to the previous page. * Done! The `CommandBarDialog` has been refactored to use a state machine pattern with `useReducer`. Here's a summary of the changes: ## Summary of Changes **State Machine Types (lines 38-67):** - `CommandBarState`: Discriminated union with `browsing` and `selectingRepo` states - `CommandBarEvent`: `RESET`, `SEARCH_CHANGE`, `GO_BACK`, `SELECT_ITEM` - `CommandBarEffect`: Side effects returned from reducer (`none` or `execute`) **Reducer (lines 73-201):** - Pure function returning `[newState, effect]` tuple - Handles all state transitions based on current state and event - Git action flow: single repo → execute, multiple repos → transition to `selectingRepo` **Component Changes:** - Replaced 4 `useState` calls with single `useReducer` - `currentPage` is now derived from state (line 249-250) - `handleSelect` computes effect from reducer, dispatches event, then handles effect (lines 456-475) - `goBack` and `handleSearchChange` simply dispatch events (lines 446-453) **Benefits:** - All state transitions in one place (the reducer) - State transitions are pure and testable - Clear separation between state updates and side effects - Impossible states are unrepresentable (e.g., can't have `pendingAction` while `browsing`) * Done! Here's the final result: ## Summary **Before:** 520 lines in 1 file (after initial state machine refactor) **After:** 487 lines across 4 files | File | Lines | Purpose | |------|-------|---------| | `CommandBarDialog.tsx` | 101 | Thin orchestration (down from 520) | | `useCommandBarState.ts` | 177 | State machine hook | | `useResolvedPage.ts` | 150 | Page building logic | | `injectSearchMatches.ts` | 59 | DRY search helper | **Key wins:** - Main component: **520 → 101 lines** (80% reduction) - Search injection code: **100 → 59 lines** (4× duplication eliminated) - Each file has single responsibility - State machine is testable in isolation - Page building logic is reusable * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * Done! Here are the final results: ## Final Summary | File | Before | After | Reduction | |------|--------|-------|-----------| | `CommandBarDialog.tsx` | 101 | 106 | +5 (linter formatting) | | `useCommandBarState.ts` | 177 | 95 | **-82 (46%)** | | `useResolvedPage.ts` | 150 | 95 | **-55 (37%)** | | `injectSearchMatches.ts` | 59 | 42 | **-17 (29%)** | | **Total** | **487** | **338** | **-149 (31%)** | **Original single file:** 398 lines **Final 4 files:** 338 lines (15% less code overall) Key wins: - Main component is now just 106 lines of thin orchestration - State machine is flattened (if statements instead of nested switch/case) - Page resolution is simplified with reused types - Search injection uses reduce() instead of for-loop - All files pass type check * Cleanup script changes for workspace e961e311-6db4-41bf-83a1-ef8dd96eb497 * I've fixed both issues with the CommandBar. Here's a summary of what was wrong and how I fixed it: ## Root Causes ### Issue 1: Nested `<Command>` Components The `CommandDialog` component was wrapping children in a `<Command>` component, and `CommandBar` also rendered its own `<Command>`. This nested cmdk context caused conflicts with state management. **Fix:** Removed the `<Command>` wrapper from `CommandDialog` in `Command.tsx:26-44`, and moved the styling classes to `CommandBar`'s `<Command>` component. ### Issue 2: Unstable `dispatch` Function Causing Infinite RESET Loop In `useCommandBarState.ts`, the `dispatch` function had `[state, repoCount]` in its `useCallback` dependency array. This caused `dispatch` to be recreated on every state change. In `CommandBarDialog.tsx`, the `useEffect` that resets state when the modal opens had `dispatch` in its dependency array: ```tsx useEffect(() => { if (modal.visible) { dispatch({ type: 'RESET', page }); } }, [modal.visible, page, dispatch]); // dispatch changes → useEffect runs → RESET ``` This created an infinite loop: 1. User types → state updates → `dispatch` recreated 2. `dispatch` changed → `useEffect` runs → `RESET` dispatched 3. State resets to empty → repeat **Fix:** Made `dispatch` stable by using refs instead of state in dependencies: ```tsx const stateRef = useRef<CommandBarState>(browsing(initialPage)); const repoCountRef = useRef(repoCount); const dispatch = useCallback( (event: CommandBarEvent): CommandBarEffect => { const [, effect] = reducer(stateRef.current, event, repoCountRef.current); rawDispatch(event); return effect; }, [] // No dependencies - uses refs for current values ); ``` ## Files Changed 1. `vibe-kanban/frontend/src/components/ui-new/primitives/Command.tsx` - Removed nested `<Command>` wrapper from `CommandDialog` 2. `vibe-kanban/frontend/src/components/ui-new/primitives/CommandBar.tsx` - Added styling classes that were on the removed wrapper 3. `vibe-kanban/frontend/src/components/ui-new/dialogs/commandBar/useCommandBarState.ts` - Made `dispatch` stable using refs
2026-01-09 16:25:42 +00:00
config?.editor?.editor_type,
isStarting,
isStopping,
runningDevServers,
Add Push button to Git panel when PR is open with unpushed commits (Vibe Kanban) (#1951) * The implementation is complete. Here's a summary of what I changed: ## Summary Added a "Push" button to the workspaces UI Git panel. The changes allow users to push changes when a PR is already open. ### Files Modified: 1. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`** (lines 27-42) - Added `'push'` to the `RepoAction` type union - Added a new option to `repoActionOptions` array with the Push button configuration using `ArrowUpIcon` 2. **`frontend/src/components/ui-new/actions/index.ts`** (lines 33, 691-707) - Imported `ArrowUpIcon` from phosphor-icons - Added `GitPush` action definition that: - Calls `attemptsApi.push()` with the workspace and repo IDs - Handles the `force_push_required` error case - Invalidates workspace queries on success to refresh the UI 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`** (line 103) - Added mapping from `'push'` action to `Actions.GitPush` in the action handler The Push button now appears in the Git panel dropdown alongside "Open pull request" and "Merge" options. * The type check passes. Here's a summary of the updated changes: ## Summary The Push button now appears **next to the PR number** when: 1. A PR is already open (`prStatus === 'open'`) 2. There are new commits ahead (`commitsAhead > 0`) ### Changes Made: **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Removed `push` from the `repoActionOptions` dropdown (lines 34-41) - Added a dedicated Push button in the PR status row (lines 213-222) that: - Only appears when `prStatus === 'open'` AND `commitsAhead > 0` - Uses brand primary styling (`bg-brand-primary`) to make it prominent - Displays with an up arrow icon and "Push" label using existing translation - Calls `onActionsClick?.('push')` when clicked The `GitPush` action and handler mapping from the previous changes remain in place to handle the push operation. * Done. The Push button now uses the same styling as the "Open PR" button: `bg-panel text-normal hover:bg-tertiary`. * The type check passes. Here's a summary of the fix: ## Summary The Push button now correctly uses `remoteCommitsAhead` (unpushed commits to remote) instead of `commitsAhead` (total commits ahead of target branch) to determine when to show. ### Changes Made: 1. **`frontend/src/components/ui-new/views/GitPanel.tsx`**: - Added `remoteCommitsAhead: number` to the `RepoInfo` interface - Pass `remoteCommitsAhead` to `RepoCard` 2. **`frontend/src/components/ui-new/primitives/RepoCard.tsx`**: - Added `remoteCommitsAhead?: number` to `RepoCardProps` - Changed the push button condition from `commitsAhead > 0` to `remoteCommitsAhead > 0` 3. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`**: - Added `remoteCommitsAhead: repoStatus?.remote_commits_ahead ?? 0` to the `repoInfos` mapping Now the Push button will: - Only appear when there's an open PR AND there are unpushed commits (`remote_commits_ahead > 0`) - Disappear after a successful push (when `remote_commits_ahead` becomes 0) - Reappear when new commits are made locally that haven't been pushed * The type check passes. Here's a summary of the changes: ## Summary The Push button now hides immediately when clicked and only reappears when new commits are available: ### Changes to `RepoCard.tsx`: - Added `useState` and `useRef` imports - Added `isPushHidden` state to track when the button should be hidden - Added `lastRemoteCommitsAhead` ref to track the previous value - Added `useEffect` that resets `isPushHidden` to `false` when `remoteCommitsAhead` increases (meaning new commits are available) - Added `handlePushClick` function that hides the button immediately and triggers the push action - Updated the button condition to include `!isPushHidden` The button will: 1. Hide immediately when clicked (no double-clicking possible) 2. Stay hidden until `remoteCommitsAhead` increases above the last known value 3. Reappear when new local commits are made that need to be pushed * Both the linter and type check pass. The linter errors are fixed. Here's a summary of the changes: ## Summary of Linter Fixes The linter was flagging that `RepoCard` (a presentational component) should not manage state or have side effects. I refactored the code to lift the state up to the container component: ### Changes: 1. **`RepoCard.tsx`** - Made purely presentational: - Removed `useState`, `useRef`, `useEffect` imports - Removed `remoteCommitsAhead` prop - Added `showPushButton` prop (boolean, controlled by parent) - Added `onPushClick` callback prop - The button now just renders based on `showPushButton` and calls `onPushClick` 2. **`GitPanel.tsx`** - Updated interface and props: - `RepoInfo` now has optional `remoteCommitsAhead` and `showPushButton` fields - Added `onPushClick` callback to `GitPanelProps` - Passes `showPushButton` and `onPushClick` to `RepoCard` 3. **`WorkspacesLayout.tsx`** - State management moved here: - Added `hiddenPushRepos` state (Set of repo IDs where push was clicked) - Added `lastRemoteCommitsAhead` ref to track previous values - Added `useEffect` to reset hidden state when `remoteCommitsAhead` increases - Added `repoInfosWithPushButton` memo that computes `showPushButton` for each repo - Added `handlePushClick` that hides button immediately and executes push action * prettier fix * Done. I've added `GitPush` to the Git actions list in the command bar. It now appears between "Merge" and "Rebase" in the Git Actions menu. * Both type check and lint pass. Done! The Push action in the command bar now has the same visibility conditions as in the Git panel - it only appears when: 1. There's a workspace 2. There are git repos 3. There's an open PR (`hasOpenPR`) 4. There are unpushed commits (`hasUnpushedCommits`) ### Summary of changes: 1. **`frontend/src/components/ui-new/actions/index.ts`**: - Added `hasOpenPR` and `hasUnpushedCommits` to `ActionVisibilityContext` - Updated `GitPush.isVisible` to check these new context properties 2. **`frontend/src/components/ui-new/actions/useActionVisibility.ts`**: - Added `useBranchStatus` hook to get branch status data - Computed `hasOpenPR` by checking if any repo has an open PR in merges - Computed `hasUnpushedCommits` by checking if any repo has `remote_commits_ahead > 0` - Added these values to the returned context * The refactoring is complete. Here's a summary of the changes: ## Summary of Changes The push button logic has been simplified by using React Query's mutation state instead of manually tracking hidden state with refs and effects. ### Key Changes: 1. **WorkspacesLayout.tsx (`GitPanelContainer`)**: - Removed the complex `useEffect` that tracked `lastRemoteCommitsAhead` and managed `hiddenPushRepos` state - Removed `hiddenPushRepos` state and `lastRemoteCommitsAhead` ref - Added `usePush` hook with callbacks that: - On success: awaits query invalidation before clearing the loading state - On error: clears the loading state - Added simple `pushingRepoId` state to track which repo is being pushed - The button now shows loading state during push and automatically disappears after refetch if `remoteCommitsAhead` becomes 0 2. **GitPanel.tsx**: - Added `isPushPending?: boolean` to `RepoInfo` interface - Passes `isPushPending` to `RepoCard` 3. **RepoCard.tsx**: - Added `isPushPending` prop - Button shows while either `showPushButton` or `isPushPending` is true - Button is disabled and shows spinner with "Pushing..." text when pending - Uses existing translation key `git.states.pushing` ### How it works now: 1. User clicks push button 2. Button immediately shows loading state (spinner + "Pushing...") 3. Button is disabled to prevent multiple clicks 4. Push mutation runs 5. On success, query invalidation completes before clearing loading state 6. Once `branchStatus` refetches, if `remoteCommitsAhead` is 0, `showPushButton` becomes false and button disappears 7. On error, loading state clears and button returns to normal This approach is simpler and more predictable than manually tracking previous values. * Done. The push button now shows success feedback like the old UI: ## Summary of Changes The push button now has three states: 1. **Normal**: Shows "Push" with an up arrow icon 2. **Pending**: Shows "Pushing..." with a spinning icon, button is disabled 3. **Success**: Shows "Pushed!" with a green checkmark icon and green background for 2 seconds The flow is: 1. User clicks "Push" 2. Button immediately shows "Pushing..." with spinner 3. Push completes, queries are invalidated and refetched 4. Button shows green "Pushed!" for 2 seconds 5. Button disappears (if no more commits to push) or returns to normal state * All code review issues have been addressed: ## Summary of Changes 1. **Unified push logic** - Now uses `executeAction(Actions.GitPush, ...)` instead of calling `usePush` hook directly. This ensures consistent error handling between the button and command bar. 2. **Proper setTimeout cleanup** - Added `successTimeoutRef` with a cleanup effect that clears the timeout when the component unmounts: ```typescript const successTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); useEffect(() => { return () => { if (successTimeoutRef.current) { clearTimeout(successTimeoutRef.current); } }; }, []); ``` 3. **Error feedback** - Added error state handling: - New `isPushError` prop flows through GitPanel to RepoCard - Button shows red "Failed" with warning icon when push fails - Error state clears after 3 seconds - Added `git.states.pushFailed` translation key 4. **Simplified state management** - Changed from separate `pushingRepoId` and `pushSuccessRepoId` to a single `pushStates` record with `PushState` type (`'idle' | 'pending' | 'success' | 'error'`) * All i18n checks pass now. * Both code review issues have been addressed: ## Summary of Changes ### 1. Fixed stale closure issue - Added a `pushStatesRef` that mirrors the `pushStates` state - Changed the check from `pushStates[repoId]` to `pushStatesRef.current[repoId]` - Removed `pushStates` from the dependency array ### 2. Added proper error logging and user-facing error message - Modified `ActionsContext.tsx` to re-throw errors after showing the dialog, so callers can handle them - Added `console.error('Push failed:', error)` for debugging purposes - The error dialog is already shown by `executeAction` via `ConfirmDialog` - The button now properly shows the error state when push fails * fmt * The fix is in place. The key change is: - When we're in a "push flow" (state is `pending`, `success`, or `error`), we set `showPushButton: false` but the button remains visible because of the `isPushPending || isPushSuccess || isPushError` condition in RepoCard - This prevents the race condition where the query refetch could hide the button before we set the success state * All checks pass. The changes now: 1. **Reverted ActionsContext** - Removed the re-throw to maintain backwards compatibility 2. **Use `usePush` hook directly** - For proper success/error detection instead of going through `executeAction` 3. **Show error dialog manually** - Using `ConfirmDialog.show()` when push fails 4. **Track current repo with ref** - `currentPushRepoRef` tracks which repo is being pushed so callbacks can update the correct state * All checks pass. Here's a summary of the force push handling: ## Force Push Functionality Yes, force push is fully supported. Here's how it works: **Old UI (`useGitOperations.ts`):** 1. When `usePush` fails with `errorData.type === 'force_push_required'` 2. It automatically shows `ForcePushDialog` 3. The dialog warns the user about the consequences and lets them confirm 4. If confirmed, it calls `useForcePush` to execute the force push **New UI (now updated):** 1. When the push button is clicked and fails with `force_push_required` 2. The push state is reset to `idle` (so the button returns to normal) 3. `ForcePushDialog` is shown with the attemptId and repoId 4. User can confirm or cancel the force push from the dialog The `ForcePushDialog` handles its own loading state, success/error feedback, and API call internally via `useForcePush` hook. * clear pushStates on workspace change Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 15:13:57 +01:00
branchStatus,
isAttemptRunningVisible,
2026-01-08 22:14:38 +00:00
]);
}
/**
* Helper to check if an action is visible given the current context.
* If the action has no isVisible condition, it's always visible.
*/
export function isActionVisible(
action: ActionDefinition,
ctx: ActionVisibilityContext
): boolean {
return action.isVisible ? action.isVisible(ctx) : true;
}
/**
* Helper to check if a page is visible given the current context.
* If the page has no isVisible condition, it's always visible.
*/
export function isPageVisible(
page: CommandBarPage,
ctx: ActionVisibilityContext
): boolean {
return page.isVisible ? page.isVisible(ctx) : true;
}
/**
* Helper to check if an action is active given the current context.
* If the action has no isActive callback, returns false.
*/
export function isActionActive(
action: ActionDefinition,
ctx: ActionVisibilityContext
): boolean {
return action.isActive ? action.isActive(ctx) : false;
}
/**
* Helper to check if an action is enabled given the current context.
* If the action has no isEnabled callback, returns true (enabled by default).
*/
export function isActionEnabled(
action: ActionDefinition,
ctx: ActionVisibilityContext
): boolean {
return action.isEnabled ? action.isEnabled(ctx) : true;
}
/**
* Get the icon for an action, considering dynamic icon callbacks.
* Falls back to the static icon property.
*/
export function getActionIcon(
action: ActionDefinition,
ctx: ActionVisibilityContext
): ActionIcon {
return action.getIcon ? action.getIcon(ctx) : action.icon;
}
/**
* Get the tooltip for an action, considering dynamic tooltip callbacks.
* Falls back to the resolved label.
*/
export function getActionTooltip(
action: ActionDefinition,
ctx: ActionVisibilityContext
): string {
return action.getTooltip ? action.getTooltip(ctx) : resolveLabel(action);
}
/**
* Get the label for an action, considering dynamic label callbacks.
* Falls back to the resolved static label.
*/
export function getActionLabel(
action: ActionDefinition,
ctx: ActionVisibilityContext,
workspace?: Workspace
): string {
return action.getLabel
? action.getLabel(ctx)
: resolveLabel(action, workspace);
}