2026-01-08 22:14:38 +00:00
|
|
|
import { useMemo } from 'react';
|
|
|
|
|
import { useUiPreferencesStore } from '@/stores/useUiPreferencesStore';
|
2026-01-14 22:07:00 +00:00
|
|
|
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';
|
2026-01-12 21:00:47 +00:00
|
|
|
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 {
|
2026-01-14 22:07:00 +00:00
|
|
|
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();
|
2026-01-11 08:03:19 +00:00
|
|
|
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);
|
2026-01-12 21:00:47 +00:00
|
|
|
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'
|
2026-01-11 08:03:19 +00:00
|
|
|
: 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 {
|
2026-01-14 22:07:00 +00:00
|
|
|
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,
|
2026-01-11 08:03:19 +00:00
|
|
|
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,
|
2026-01-12 21:00:47 +00:00
|
|
|
isAttemptRunning: isAttemptRunningVisible,
|
2026-01-08 22:14:38 +00:00
|
|
|
};
|
|
|
|
|
}, [
|
2026-01-14 22:07:00 +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,
|
2026-01-11 08:03:19 +00:00
|
|
|
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,
|
2026-01-12 21:00:47 +00:00
|
|
|
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);
|
|
|
|
|
}
|