From 90c95daae6de4af2b2f20f2ec5d54e1e51128f0a Mon Sep 17 00:00:00 2001 From: guantw Date: Fri, 23 Oct 2026 21:52:45 +0900 Subject: [PATCH] fix(ui): remove Git panel entry points and shortcuts Remove Git panel navigation from global shortcuts, capability search, and the unused IDE helper. Reject presentation requests for the retired panel while preserving host Git capabilities. Remove the six panel-scoped shortcut definitions and registrations, including the commit input key handler. Independent branch switching and Git operations remain available. --- .../interactiveCapabilityActivator.test.ts | 8 ++ .../interactiveCapabilityActivator.ts | 8 +- .../interactiveCapabilityCatalog.ts | 7 ++ ...nteractiveCapabilitySearchProvider.test.ts | 6 ++ .../interactiveCapabilitySearchProvider.ts | 6 +- .../src/app/hooks/useGlobalSceneShortcuts.ts | 8 -- .../app/scenes/git/views/WorkingCopyView.tsx | 79 +------------------ src/web-ui/src/shared/constants/shortcuts.ts | 41 ---------- .../src/shared/services/ide-control/api.ts | 4 - 9 files changed, 34 insertions(+), 133 deletions(-) diff --git a/src/web-ui/src/app/global-search/interactiveCapabilityActivator.test.ts b/src/web-ui/src/app/global-search/interactiveCapabilityActivator.test.ts index ff3db7b970..444d99d185 100644 --- a/src/web-ui/src/app/global-search/interactiveCapabilityActivator.test.ts +++ b/src/web-ui/src/app/global-search/interactiveCapabilityActivator.test.ts @@ -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(); + }); }); diff --git a/src/web-ui/src/app/global-search/interactiveCapabilityActivator.ts b/src/web-ui/src/app/global-search/interactiveCapabilityActivator.ts index 840543ac6b..0d2f6daba6 100644 --- a/src/web-ui/src/app/global-search/interactiveCapabilityActivator.ts +++ b/src/web-ui/src/app/global-search/interactiveCapabilityActivator.ts @@ -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 { @@ -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': diff --git a/src/web-ui/src/app/global-search/interactiveCapabilityCatalog.ts b/src/web-ui/src/app/global-search/interactiveCapabilityCatalog.ts index 6d4633c377..89ab922d10 100644 --- a/src/web-ui/src/app/global-search/interactiveCapabilityCatalog.ts +++ b/src/web-ui/src/app/global-search/interactiveCapabilityCatalog.ts @@ -14,6 +14,13 @@ export type InteractiveCapabilityDestination = | { kind: 'scene'; sceneId: SceneTabId } | { kind: 'event'; eventName: string; detail?: Record }; +/** 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; diff --git a/src/web-ui/src/app/global-search/providers/interactiveCapabilitySearchProvider.test.ts b/src/web-ui/src/app/global-search/providers/interactiveCapabilitySearchProvider.test.ts index c8517b9b62..bee2f55c32 100644 --- a/src/web-ui/src/app/global-search/providers/interactiveCapabilitySearchProvider.test.ts +++ b/src/web-ui/src/app/global-search/providers/interactiveCapabilitySearchProvider.test.ts @@ -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, diff --git a/src/web-ui/src/app/global-search/providers/interactiveCapabilitySearchProvider.ts b/src/web-ui/src/app/global-search/providers/interactiveCapabilitySearchProvider.ts index 0b2809201e..0a6069b07f 100644 --- a/src/web-ui/src/app/global-search/providers/interactiveCapabilitySearchProvider.ts +++ b/src/web-ui/src/app/global-search/providers/interactiveCapabilitySearchProvider.ts @@ -1,6 +1,7 @@ import { INTERACTIVE_CAPABILITY_CATALOG, getInteractiveCapabilityCatalog, + isInteractiveCapabilityDestinationAvailable, type InteractiveCapability, } from '../interactiveCapabilityCatalog'; import { scoreTextMatch } from '../searchMatching'; @@ -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, diff --git a/src/web-ui/src/app/hooks/useGlobalSceneShortcuts.ts b/src/web-ui/src/app/hooks/useGlobalSceneShortcuts.ts index a75d7ca802..9ed9ec1d66 100644 --- a/src/web-ui/src/app/hooks/useGlobalSceneShortcuts.ts +++ b/src/web-ui/src/app/hooks/useGlobalSceneShortcuts.ts @@ -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 */ @@ -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 }, diff --git a/src/web-ui/src/app/scenes/git/views/WorkingCopyView.tsx b/src/web-ui/src/app/scenes/git/views/WorkingCopyView.tsx index 9d30e2f189..cef47b8ae1 100644 --- a/src/web-ui/src/app/scenes/git/views/WorkingCopyView.tsx +++ b/src/web-ui/src/app/scenes/git/views/WorkingCopyView.tsx @@ -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'; @@ -86,7 +85,7 @@ const WorkingCopyView: React.FC = ({ [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, }); @@ -306,76 +305,6 @@ const WorkingCopyView: React.FC = ({ 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 (
@@ -431,12 +360,6 @@ const WorkingCopyView: React.FC = ({ 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 ? ( diff --git a/src/web-ui/src/shared/constants/shortcuts.ts b/src/web-ui/src/shared/constants/shortcuts.ts index 8471f3201a..6fae6462c0 100644 --- a/src/web-ui/src/shared/constants/shortcuts.ts +++ b/src/web-ui/src/shared/constants/shortcuts.ts @@ -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 }), @@ -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[] = [ @@ -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). */ diff --git a/src/web-ui/src/shared/services/ide-control/api.ts b/src/web-ui/src/shared/services/ide-control/api.ts index 4efb88411c..8b5371c9c6 100644 --- a/src/web-ui/src/shared/services/ide-control/api.ts +++ b/src/web-ui/src/shared/services/ide-control/api.ts @@ -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')