Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,12 @@ describe('activateInteractiveCapability', () => {
})).rejects.toThrow('Unknown documented item');
expect(mocks.openDestination).not.toHaveBeenCalled();
});

it('rejects Git panel requests, including documented items, without opening a scene', async () => {
for (const itemId of [undefined, 'commit', 'status-diff']) {
await expect(activateInteractiveCapability('feature.git', { itemId }))
.rejects.toThrow('The Git panel is no longer available');
}
expect(mocks.openScene).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useSettingsStore } from '@/app/scenes/settings/settingsStore';
import { useSceneStore } from '@/app/stores/sceneStore';
import { getInteractiveCapability } from './interactiveCapabilityCatalog';
import {
getInteractiveCapability,
isInteractiveCapabilityDestinationAvailable,
} from './interactiveCapabilityCatalog';
import { activateProductAction } from './productActionActivator';

export interface InteractiveCapabilityActivationOptions {
Expand All @@ -21,6 +24,9 @@ export async function activateInteractiveCapability(
throw new Error(`Unknown documented item for ${capabilityId}: ${options.itemId}`);
}
const destination = item?.destination ?? capability.destination;
if (!isInteractiveCapabilityDestinationAvailable(destination)) {
throw new Error('The Git panel is no longer available');
}

switch (destination.kind) {
case 'settings':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export type InteractiveCapabilityDestination =
| { kind: 'scene'; sceneId: SceneTabId }
| { kind: 'event'; eventName: string; detail?: Record<string, unknown> };

/** Git operations remain host capabilities, but their standalone panel has no entry point. */
export function isInteractiveCapabilityDestinationAvailable(
destination: InteractiveCapabilityDestination,
): boolean {
return destination.kind !== 'scene' || destination.sceneId !== 'git';
}

export interface InteractiveCapabilityValueSchema {
type: 'boolean' | 'string' | 'integer' | 'number' | 'object' | 'array';
nullable?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ describe('interactiveCapabilitySearchProvider', () => {
request(acceptance.query),
new AbortController().signal,
);
// Git remains in the host discovery contract, but no longer has a Web UI panel entry.
if (acceptance.expectedFirstCapabilityId === 'feature.git') {
expect(result.items.some(item => item.target.kind === 'capability'
&& item.target.capabilityId === 'feature.git')).toBe(false);
continue;
}
const ranked = [...result.items].sort((left, right) => right.score - left.score);
expect(
ranked[0]?.target.kind === 'capability' ? ranked[0].target.capabilityId : undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
INTERACTIVE_CAPABILITY_CATALOG,
getInteractiveCapabilityCatalog,
isInteractiveCapabilityDestinationAvailable,
type InteractiveCapability,
} from '../interactiveCapabilityCatalog';
import { scoreTextMatch } from '../searchMatching';
Expand Down Expand Up @@ -51,8 +52,11 @@ export const interactiveCapabilitySearchProvider: GlobalSearchProvider = {
? getInteractiveCapabilityCatalog(false)
: INTERACTIVE_CAPABILITY_CATALOG;
const items = catalog.capabilities
.map((capability) => {
.flatMap((capability) => {
const item = matchingItem(capability, request.query);
if (!isInteractiveCapabilityDestinationAvailable(item?.destination ?? capability.destination)) {
return [];
}
const textScore = scoreTextMatch(request.query, [
capability.id,
capability.titleZh,
Expand Down
8 changes: 0 additions & 8 deletions src/web-ui/src/app/hooks/useGlobalSceneShortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*
* Scene quick-open (allowInInput: true — fires from anywhere):
* Mod+Shift+A — open Agent/Session scene
* Mod+Shift+G — open Git scene
* Mod+, — open Settings scene
* Mod+Shift+` — open Terminal scene
*/
Expand Down Expand Up @@ -51,13 +50,6 @@ export function useGlobalSceneShortcuts(): void {
{ priority: 10, description: 'keyboard.shortcuts.scene.openSession' }
);

useShortcut(
'scene.openGit',
{ key: 'G', ctrl: true, shift: true, scope: 'app', allowInInput: true },
() => openSceneById('git'),
{ priority: 10, description: 'keyboard.shortcuts.scene.openGit' }
);

useShortcut(
'scene.openSettings',
{ key: ',', ctrl: true, scope: 'app', allowInInput: true },
Expand Down
79 changes: 1 addition & 78 deletions src/web-ui/src/app/scenes/git/views/WorkingCopyView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import { OverflowText, Button, Icon, IconButton, SearchField, Textarea, Tooltip } from '@openbitfun/ui';
import React, { useCallback, useState, useMemo, useEffect, useRef } from 'react';
import { useShortcut } from '@/infrastructure/hooks/useShortcut';
import { useTranslation } from 'react-i18next';
import { Minus, RotateCcw, Square, FileCode2 } from 'lucide-react';
import { useI18n } from '@/infrastructure/i18n/hooks/useI18n';
Expand Down Expand Up @@ -86,7 +85,7 @@ const WorkingCopyView: React.FC<WorkingCopyViewProps> = ({
[currentBranch, staged, unstaged, untracked, ahead, behind]
);

const { isOperating, addFiles, commit, push, pull, resetFiles } = useGitOperations({
const { isOperating, addFiles, commit, push, pull } = useGitOperations({
repositoryPath: { workspaceId: workspaceId ?? '', repositoryPath: workspacePath },
autoRefresh: false,
});
Expand Down Expand Up @@ -306,76 +305,6 @@ const WorkingCopyView: React.FC<WorkingCopyViewProps> = ({
document.addEventListener('mouseup', onMouseUp);
}, [fileListWidth]);

const handleGitStageAll = useCallback(async () => {
const files = getAllUnstagedFiles();
if (files.length === 0) return;
const result = await addFiles({ files, all: false });
if (result.success) {
setSelectedFiles(new Set());
await handleRefresh();
notification.success(t('notifications.stageSuccess', { count: files.length }));
} else if (result.error) notification.error(t('notifications.stageFailed', { error: result.error }));
}, [getAllUnstagedFiles, addFiles, handleRefresh, notification, t]);

const handleGitUnstageAll = useCallback(async () => {
if (!status?.staged?.length) return;
const paths = status.staged.map((f: { path: string }) => f.path);
const result = await resetFiles(paths, true);
if (result.success) await handleRefresh();
else if (result.error) notification.error(result.error);
}, [status, resetFiles, handleRefresh, notification]);

const gitShortcutsEnabled = Boolean(workspacePath) && isActive;

useShortcut(
'git.refresh',
{ key: 'F5', scope: 'git' },
() => {
void handleRefresh();
},
{ enabled: gitShortcutsEnabled, description: 'keyboard.shortcuts.git.refresh' }
);
useShortcut(
'git.commit',
{ key: 'Enter', ctrl: true, scope: 'git', allowInInput: true },
() => {
void handleQuickCommit();
},
{ enabled: gitShortcutsEnabled, description: 'keyboard.shortcuts.git.commit' }
);
useShortcut(
'git.push',
{ key: 'P', ctrl: true, shift: true, scope: 'git' },
() => {
void handlePush();
},
{ enabled: gitShortcutsEnabled, description: 'keyboard.shortcuts.git.push' }
);
useShortcut(
'git.pull',
{ key: 'L', ctrl: true, shift: true, scope: 'git' },
() => {
void handlePull();
},
{ enabled: gitShortcutsEnabled, description: 'keyboard.shortcuts.git.pull' }
);
useShortcut(
'git.stageAll',
{ key: 'A', ctrl: true, shift: true, scope: 'git' },
() => {
void handleGitStageAll();
},
{ enabled: gitShortcutsEnabled, description: 'keyboard.shortcuts.git.stageAll' }
);
useShortcut(
'git.unstageAll',
{ key: 'U', ctrl: true, shift: true, scope: 'git' },
() => {
void handleGitUnstageAll();
},
{ enabled: gitShortcutsEnabled, description: 'keyboard.shortcuts.git.unstageAll' }
);

if (!workspacePath) {
return (
<div data-openbitfun-component="working-copy-view" data-openbitfun-part="root" className="openbitfun-git-scene-working-copy">
Expand Down Expand Up @@ -431,12 +360,6 @@ const WorkingCopyView: React.FC<WorkingCopyViewProps> = ({
placeholder={status?.staged?.length ? t('commit.inputPlaceholder') : t('commit.inputPlaceholderNoStaged')}
value={quickCommitMessage}
onChange={e => setQuickCommitMessage(e.target.value)}
onKeyDown={e => {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
handleQuickCommit();
}
}}
disabled={isOperating || isGeneratingCommit}
/>
{isGeneratingCommit ? (
Expand Down
41 changes: 0 additions & 41 deletions src/web-ui/src/shared/constants/shortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@ export const APP_SHORTCUTS: ShortcutDef[] = [
config: mod('A', { shift: true, scope: 'app', allowInInput: true }),
descriptionKey: 'keyboard.shortcuts.scene.openSession',
},
{
id: 'scene.openGit',
config: mod('G', { shift: true, scope: 'app', allowInInput: true }),
descriptionKey: 'keyboard.shortcuts.scene.openGit',
},
{
id: 'scene.openSettings',
config: mod(',', { scope: 'app', allowInInput: true }),
Expand Down Expand Up @@ -250,41 +245,6 @@ export const FILETREE_SHORTCUTS: ShortcutDef[] = [
},
];

// ─── Git shortcuts (scope: 'git') ─────────────────────────────────────────

export const GIT_SHORTCUTS: ShortcutDef[] = [
{
id: 'git.commit',
config: mod('Enter', { scope: 'git', allowInInput: true }),
descriptionKey: 'keyboard.shortcuts.git.commit',
},
{
id: 'git.refresh',
config: { key: 'F5', scope: 'git' },
descriptionKey: 'keyboard.shortcuts.git.refresh',
},
{
id: 'git.stageAll',
config: mod('A', { shift: true, scope: 'git' }),
descriptionKey: 'keyboard.shortcuts.git.stageAll',
},
{
id: 'git.unstageAll',
config: mod('U', { shift: true, scope: 'git' }),
descriptionKey: 'keyboard.shortcuts.git.unstageAll',
},
{
id: 'git.push',
config: mod('P', { shift: true, scope: 'git' }),
descriptionKey: 'keyboard.shortcuts.git.push',
},
{
id: 'git.pull',
config: mod('L', { shift: true, scope: 'git' }),
descriptionKey: 'keyboard.shortcuts.git.pull',
},
];

// ─── All shortcuts combined ────────────────────────────────────────────────

export const ALL_SHORTCUTS: ShortcutDef[] = [
Expand All @@ -294,7 +254,6 @@ export const ALL_SHORTCUTS: ShortcutDef[] = [
...EDITOR_SHORTCUTS,
...CHAT_SHORTCUTS,
...FILETREE_SHORTCUTS,
...GIT_SHORTCUTS,
];

/** Shortcuts registered in code but not listed in ALL_SHORTCUTS (e.g. legacy ids). */
Expand Down
4 changes: 0 additions & 4 deletions src/web-ui/src/shared/services/ide-control/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,6 @@ export const ideControl = {

export const quickActions = {

openGitPanel: () => window.dispatchEvent(new CustomEvent('scene:open', { detail: { sceneId: 'git' } })),



openSettings: (destination?: SettingsDestination | string) => {
if (isLegacyEcosystemCompatibilityDestination(destination)) {
void import('@/app/scenes/ecosystem-compatibility/ecosystemCompatibilityStore')
Expand Down
Loading