Files
openchamber/packages/ui/src/hooks/useKeyboardShortcuts.ts
T
Bohdan Triapitsyn ce90e7e24f feat(settings): Session tabs toggle in General → Navigation
A new Session tabs group (web/desktop only) turns the header session
tabs off; disabled, the header renders the exact pre-tabs view — plain
session title with meta row and the always-visible session menu (the
same block VS Code uses). The Alt+W close-tab shortcut no-ops while
tabs are off. Registered in settings search with a matching anchor;
labels translated in all locales.
2026-08-25 00:05:45 +03:00

723 lines
25 KiB
TypeScript

import React from 'react';
import { isTerminalEventTarget } from '@/lib/terminalFocus';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { closeSessionTabAndActivateNeighbour } from '@/lib/sessionTabs';
import { useSelectionStore } from '@/sync/selection-store';
import * as sessionActions from '@/sync/session-actions';
import { normalizeContextPanelDirectoryKey, useUIStore } from '@/stores/useUIStore';
import { useThemeSystem } from '@/contexts/useThemeSystem';
import { useCurrentSessionActivity } from '@/hooks/useSessionActivity';
import { createWorktreeSession } from '@/lib/worktreeSessionCreator';
import { useConfigStore } from '@/stores/useConfigStore';
import { canUseElectronDesktopIPC, invokeDesktop, isVSCodeRuntime } from '@/lib/desktop';
import { showOpenCodeStatus } from '@/lib/openCodeStatus';
import {
eventMatchesShortcut,
eventMatchesShortcutPrefix,
getEffectiveShortcutCombo,
getEffectiveShortcutPrefix,
normalizeCombo,
} from '@/lib/shortcuts';
import { getVisibleContextRailSurfaces } from '@/lib/surfaces/registry';
import { readEmbeddedThemeSearchParams } from '@/contexts/theme-embedded-bootstrap';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
import { useProjectsStore } from '@/stores/useProjectsStore';
import { useFeatureFlagsStore } from '@/stores/useFeatureFlagsStore';
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
import { getCycledPrimaryAgentName } from '@/components/chat/mobileControlsUtils';
import { focusChatInput } from '@/components/chat/composer/editor/dom';
import { addSelectionToChat } from '@/lib/addSelectionToChat';
import { hasOpenDropdown } from './keyboard-shortcut-dom';
export const useKeyboardShortcuts = () => {
const openNewSessionDraft = useSessionUIStore((s) => s.openNewSessionDraft);
const armAbortPrompt = useSessionUIStore((s) => s.armAbortPrompt);
const clearAbortPrompt = useSessionUIStore((s) => s.clearAbortPrompt);
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
const abortCurrentOperation = sessionActions.abortCurrentOperation;
const toggleCommandPalette = useUIStore((s) => s.toggleCommandPalette);
const toggleHelpDialog = useUIStore((s) => s.toggleHelpDialog);
const toggleSidebar = useUIStore((s) => s.toggleSidebar);
const currentShortcutDirectory = useDirectoryStore((s) => s.currentDirectory);
const effectiveDirectory = useEffectiveDirectory();
// The terminal lives in the context panel; these mirror the rail behavior.
const toggleTerminalSurface = React.useCallback(() => {
if (!currentShortcutDirectory) return;
useUIStore.getState().openContextSurface(normalizeContextPanelDirectoryKey(currentShortcutDirectory), 'terminal');
}, [currentShortcutDirectory]);
const toggleTerminalSurfaceExpanded = React.useCallback(() => {
if (!currentShortcutDirectory) return;
const key = normalizeContextPanelDirectoryKey(currentShortcutDirectory);
const state = useUIStore.getState();
const panel = state.contextPanelByDirectory[key];
const activeMode = panel?.isOpen ? panel.tabs.find((tab) => tab.id === panel.activeTabId)?.mode : null;
if (activeMode !== 'terminal') {
state.openContextSurface(key, 'terminal');
}
state.toggleContextPanelExpanded(key);
}, [currentShortcutDirectory]);
const isMobile = useUIStore((s) => s.isMobile);
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen);
const setModelSelectorOpen = useUIStore((s) => s.setModelSelectorOpen);
const setTimelineDialogOpen = useUIStore((s) => s.setTimelineDialogOpen);
const togglePromptNavigatorPanel = useUIStore((s) => s.togglePromptNavigatorPanel);
const setPromptNavigatorPanelOpen = useUIStore((s) => s.setPromptNavigatorPanelOpen);
const toggleExpandedInput = useUIStore((s) => s.toggleExpandedInput);
const shortcutOverrides = useUIStore((s) => s.shortcutOverrides);
const currentDirectory = useDirectoryStore((s) => s.currentDirectory);
const activeProject = useProjectsStore((s) => s.getActiveProject());
const { themeMode, setThemeMode } = useThemeSystem();
const { phase: sessionPhase } = useCurrentSessionActivity();
const abortPrimedUntilRef = React.useRef<number | null>(null);
const abortPrimedTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const themeModeRef = React.useRef(themeMode);
// Currently held physical keys (lowercased), used to match chord prefixes
// whose primary key must be held while the activating key is pressed.
const heldKeysRef = React.useRef<Set<string>>(new Set());
React.useEffect(() => {
themeModeRef.current = themeMode;
}, [themeMode]);
const resetAbortPriming = React.useCallback(() => {
if (abortPrimedTimeoutRef.current) {
clearTimeout(abortPrimedTimeoutRef.current);
abortPrimedTimeoutRef.current = null;
}
abortPrimedUntilRef.current = null;
clearAbortPrompt();
}, [clearAbortPrompt]);
React.useEffect(() => {
const combo = (actionId: string) => getEffectiveShortcutCombo(actionId, shortcutOverrides);
const switchSurfacePrefix = getEffectiveShortcutPrefix('switch_context_surface', shortcutOverrides);
const dropdownTargetSelector = [
'[data-slot="dropdown-menu-content"]',
'[data-slot="select-content"]',
'[role="combobox"]',
'[role="listbox"]',
'[role="menu"]',
'[role="menuitem"]',
'[role="option"]',
'[data-radix-popper-content-wrapper]',
].join(',');
const isDropdownEventTarget = (target: EventTarget | null) => {
return target instanceof Element && Boolean(target.closest(dropdownTargetSelector));
};
const handleTerminalShortcutCapture = (e: KeyboardEvent) => {
if (!isTerminalEventTarget(e.target)) {
return;
}
if (eventMatchesShortcut(e, combo('toggle_terminal'))) {
const { isMobile } = useUIStore.getState();
if (isMobile) {
return;
}
e.preventDefault();
e.stopPropagation();
toggleTerminalSurface();
return;
}
if (eventMatchesShortcut(e, combo('toggle_terminal_expanded'))) {
const { isMobile } = useUIStore.getState();
if (isMobile) {
return;
}
e.preventDefault();
e.stopPropagation();
toggleTerminalSurfaceExpanded();
return;
}
};
const handleEscapeKeyDownCapture = (e: KeyboardEvent) => {
if (e.key !== 'Escape') return;
const target = e.target as Element | null;
const isInsideDialog = Boolean(target?.closest('[role="dialog"]'));
const isSettingsMounted = Boolean(document.querySelector('[data-settings-view="true"]'));
const isInsideTerminal = isTerminalEventTarget(target);
const hasDropdownInteraction = isDropdownEventTarget(target) || hasOpenDropdown();
const {
isSettingsDialogOpen,
isCommandPaletteOpen,
isHelpDialogOpen,
isSessionSwitcherOpen,
isAboutDialogOpen,
isMultiRunLauncherOpen,
isImagePreviewOpen,
isPromptNavigatorPanelOpen,
} = useUIStore.getState();
if (isInsideDialog || isInsideTerminal || hasDropdownInteraction) {
resetAbortPriming();
return;
}
if (isPromptNavigatorPanelOpen) {
e.preventDefault();
setPromptNavigatorPanelOpen(false);
resetAbortPriming();
return;
}
if (isSettingsDialogOpen) {
e.preventDefault();
setSettingsDialogOpen(false);
resetAbortPriming();
return;
}
if (isSettingsMounted) {
resetAbortPriming();
return;
}
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen || isMultiRunLauncherOpen || isImagePreviewOpen;
const isChatActive = true;
if (hasOverlay || !isChatActive) {
resetAbortPriming();
return;
}
const sessionId = currentSessionId;
if (sessionPhase === 'idle' || !sessionId) {
resetAbortPriming();
return;
}
const now = Date.now();
const primedUntil = abortPrimedUntilRef.current;
if (primedUntil && now < primedUntil) {
e.preventDefault();
resetAbortPriming();
void abortCurrentOperation(sessionId);
return;
}
e.preventDefault();
const expiresAt = armAbortPrompt(3000) ?? now + 3000;
abortPrimedUntilRef.current = expiresAt;
if (abortPrimedTimeoutRef.current) {
clearTimeout(abortPrimedTimeoutRef.current);
}
const delay = Math.max(expiresAt - now, 0);
abortPrimedTimeoutRef.current = setTimeout(() => {
if (abortPrimedUntilRef.current && Date.now() >= abortPrimedUntilRef.current) {
resetAbortPriming();
}
}, delay || 0);
};
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape' || isTerminalEventTarget(e.target)) {
return;
}
const isChatInputTarget = (target: EventTarget | null) => {
return target instanceof Element && Boolean(target.closest('[data-chat-input="true"]'));
};
if (eventMatchesShortcut(e, combo('open_command_palette'))) {
e.preventDefault();
toggleCommandPalette();
return;
}
if (eventMatchesShortcut(e, combo('open_timeline_dialog'))) {
e.preventDefault();
setTimelineDialogOpen(true);
return;
}
if (eventMatchesShortcut(e, combo('toggle_prompt_navigator'))) {
const {
promptNavigatorEnabled,
isSettingsDialogOpen,
isCommandPaletteOpen,
isHelpDialogOpen,
isSessionSwitcherOpen,
isAboutDialogOpen,
isTimelineDialogOpen,
isMultiRunLauncherOpen,
isImagePreviewOpen,
} = useUIStore.getState();
if (!promptNavigatorEnabled || isMobile || isVSCodeRuntime()) {
return;
}
const hasOverlay = isSettingsDialogOpen
|| isCommandPaletteOpen
|| isHelpDialogOpen
|| isSessionSwitcherOpen
|| isAboutDialogOpen
|| isTimelineDialogOpen
|| isMultiRunLauncherOpen
|| isImagePreviewOpen;
if (hasOverlay) {
return;
}
e.preventDefault();
togglePromptNavigatorPanel();
return;
}
if (eventMatchesShortcut(e, combo('open_status'))) {
e.preventDefault();
void showOpenCodeStatus();
return;
}
if (eventMatchesShortcut(e, combo('open_help'))) {
e.preventDefault();
toggleHelpDialog();
return;
}
if (canUseElectronDesktopIPC() && eventMatchesShortcut(e, combo('new_mini_chat'))) {
e.preventDefault();
void invokeDesktop('desktop_open_draft_mini_chat_window', {
directory: currentDirectory || activeProject?.path || '',
projectId: activeProject?.id ?? null,
}).catch((error) => {
console.warn('[keyboard-shortcuts] failed to open draft mini chat window', error);
});
return;
}
if (!isVSCodeRuntime() && useUIStore.getState().sessionTabsEnabled && eventMatchesShortcut(e, combo('close_session_tab'))) {
e.preventDefault();
if (currentSessionId) {
closeSessionTabAndActivateNeighbour(currentSessionId);
}
return;
}
const matchedNewSessionShortcut = eventMatchesShortcut(e, combo('new_chat'));
const matchedWorktreeShortcut = eventMatchesShortcut(e, combo('new_chat_worktree'));
if (matchedNewSessionShortcut || matchedWorktreeShortcut) {
e.preventDefault();
setSessionSwitcherOpen(false);
if (!isVSCodeRuntime() && matchedWorktreeShortcut) {
createWorktreeSession();
return;
}
openNewSessionDraft(currentSessionId && currentDirectory
? { directoryOverride: currentDirectory }
: undefined);
return;
}
if (eventMatchesShortcut(e, combo('cycle_theme'))) {
e.preventDefault();
if (readEmbeddedThemeSearchParams() !== null && window.parent && window.parent !== window) {
window.parent.postMessage({ type: 'openchamber:cycle-theme-request' }, window.location.origin);
return;
}
const modes: Array<'light' | 'dark' | 'system'> = ['light', 'dark', 'system'];
const activeElement = document.activeElement as HTMLElement | null;
const currentIndex = modes.indexOf(themeModeRef.current);
const nextIndex = (currentIndex + 1) % modes.length;
setThemeMode(modes[nextIndex]);
requestAnimationFrame(() => {
if (typeof document === 'undefined' || typeof window === 'undefined') {
return;
}
if (!document.hasFocus()) {
window.focus();
}
if (activeElement && document.contains(activeElement)) {
activeElement.focus({ preventScroll: true });
}
});
return;
}
if (eventMatchesShortcut(e, combo('open_settings'))) {
e.preventDefault();
const { isSettingsDialogOpen } = useUIStore.getState();
setSettingsDialogOpen(!isSettingsDialogOpen);
return;
}
if (eventMatchesShortcut(e, combo('add_selection_to_chat'))) {
e.preventDefault();
addSelectionToChat();
return;
}
if (eventMatchesShortcut(e, combo('toggle_sidebar'))) {
e.preventDefault();
const { isMobile, isSessionSwitcherOpen } = useUIStore.getState();
if (isMobile) {
setSessionSwitcherOpen(!isSessionSwitcherOpen);
} else {
toggleSidebar();
}
return;
}
if (eventMatchesShortcut(e, combo('focus_input'))) {
e.preventDefault();
focusChatInput();
return;
}
const cycleAgentCombo = combo('cycle_agent');
const cycleAgentBackwardCombo = cycleAgentCombo && !cycleAgentCombo.includes('shift')
? normalizeCombo(`shift+${cycleAgentCombo}`)
: '';
const cycleAgentDirection = cycleAgentBackwardCombo && eventMatchesShortcut(e, cycleAgentBackwardCombo)
? -1
: eventMatchesShortcut(e, cycleAgentCombo)
? 1
: 0;
if (cycleAgentDirection !== 0) {
const {
isSettingsDialogOpen,
isCommandPaletteOpen,
isHelpDialogOpen,
isSessionSwitcherOpen,
isAboutDialogOpen,
} = useUIStore.getState();
const hasOverlay = isSettingsDialogOpen || isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
if (hasOverlay || !isChatInputTarget(e.target)) {
return;
}
const configState = useConfigStore.getState();
const nextAgentName = getCycledPrimaryAgentName(
configState.getVisibleAgents(),
configState.currentAgentName,
cycleAgentDirection,
);
if (!nextAgentName) {
return;
}
e.preventDefault();
configState.setAgent(nextAgentName);
useUIStore.getState().addRecentAgent(nextAgentName);
const sessionId = useSessionUIStore.getState().currentSessionId;
if (sessionId) {
useSelectionStore.getState().saveSessionAgentSelection(sessionId, nextAgentName);
}
return;
}
// Legacy right-sidebar shortcuts now target the context surfaces that
// replaced the sidebar's tabs.
if (eventMatchesShortcut(e, combo('toggle_right_sidebar'))) {
const state = useUIStore.getState();
if (state.isMobile || !currentDirectory) {
return;
}
e.preventDefault();
const directory = normalizeContextPanelDirectoryKey(currentDirectory);
const panelState = state.contextPanelByDirectory[directory];
if (panelState?.isOpen) {
state.closeContextPanel(directory);
} else if (panelState?.activeTabId) {
state.setActiveContextPanelTab(directory, panelState.activeTabId);
} else {
state.openContextSurface(directory, 'git');
}
return;
}
if (eventMatchesShortcut(e, combo('open_right_sidebar_git'))) {
const state = useUIStore.getState();
if (state.isMobile || !currentDirectory) {
return;
}
e.preventDefault();
state.openContextSurface(normalizeContextPanelDirectoryKey(currentDirectory), 'git');
return;
}
if (eventMatchesShortcut(e, combo('open_right_sidebar_files'))) {
const state = useUIStore.getState();
if (state.isMobile || !currentDirectory) {
return;
}
e.preventDefault();
state.openContextSurface(normalizeContextPanelDirectoryKey(currentDirectory), 'file');
return;
}
if (eventMatchesShortcut(e, combo('toggle_terminal'))) {
const { isMobile } = useUIStore.getState();
if (isMobile) {
return;
}
e.preventDefault();
toggleTerminalSurface();
return;
}
if (eventMatchesShortcut(e, combo('toggle_terminal_expanded'))) {
const { isMobile } = useUIStore.getState();
if (isMobile) {
return;
}
e.preventDefault();
toggleTerminalSurfaceExpanded();
return;
}
// Configured prefix + digit (default: Cmd/Ctrl + 1..9, with 0 for the
// 10th surface): open/close the matching context panel rail surface. The
// digit maps to the currently visible rail order, matching the number
// badges shown while holding the modifier. `e.repeat` guard keeps
// holding a digit from toggling.
const switchSurfaceDigit = e.key.length === 1 && e.key >= '0' && e.key <= '9'
? (e.key === '0' ? 10 : Number(e.key))
: null;
if (switchSurfaceDigit !== null
&& !e.repeat
&& eventMatchesShortcutPrefix(e, switchSurfacePrefix, heldKeysRef.current)) {
const state = useUIStore.getState();
if (state.isMobile || !effectiveDirectory) {
return;
}
const directory = normalizeContextPanelDirectoryKey(effectiveDirectory);
const panelState = state.contextPanelByDirectory[directory];
const visibleSurfaces = getVisibleContextRailSurfaces({
railOrder: state.contextRailOrder,
planModeEnabled: useFeatureFlagsStore.getState().planModeEnabled,
isVSCode: isVSCodeRuntime(),
screenWidth: window.innerWidth,
tabs: panelState?.tabs ?? [],
});
const target = visibleSurfaces[switchSurfaceDigit - 1];
if (!target) {
return;
}
e.preventDefault();
state.openContextSurface(directory, target.mode);
return;
}
// Cmd/Ctrl+Shift+M: Open model selector (same conditions as double-ESC: chat tab, no overlays)
if (eventMatchesShortcut(e, combo('open_model_selector'))) {
const {
isSettingsDialogOpen,
isCommandPaletteOpen,
isHelpDialogOpen,
isSessionSwitcherOpen,
isAboutDialogOpen,
isModelSelectorOpen,
} = useUIStore.getState();
// Skip if settings open
if (isSettingsDialogOpen) {
return;
}
// Skip if any overlay open or not on chat tab
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
const isChatActive = true;
if (hasOverlay || !isChatActive) {
return;
}
e.preventDefault();
setModelSelectorOpen(!isModelSelectorOpen);
return;
}
// Cmd/Ctrl+Shift+T: Cycle thinking variant (same gating as Shift+M)
if (eventMatchesShortcut(e, combo('cycle_thinking_variant'))) {
const {
isSettingsDialogOpen,
isCommandPaletteOpen,
isHelpDialogOpen,
isSessionSwitcherOpen,
isAboutDialogOpen,
} = useUIStore.getState();
if (isSettingsDialogOpen) {
return;
}
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
const isChatActive = true;
if (hasOverlay || !isChatActive) {
return;
}
const configState = useConfigStore.getState();
const variants = configState.getCurrentModelVariants();
if (variants.length === 0) {
return;
}
e.preventDefault();
configState.cycleCurrentVariant();
const nextVariant = useConfigStore.getState().currentVariant;
const sessionId = useSessionUIStore.getState().currentSessionId;
const agentName = useConfigStore.getState().currentAgentName;
const providerId = useConfigStore.getState().currentProviderId;
const modelId = useConfigStore.getState().currentModelId;
if (sessionId && agentName && providerId && modelId) {
useSelectionStore.getState().saveAgentModelVariantForSession(sessionId, agentName, providerId, modelId, nextVariant);
}
return;
}
// Ctrl+] / Ctrl+[: Cycle through starred models (same gating as Shift+M)
if (
eventMatchesShortcut(e, combo('cycle_favorite_model_forward')) ||
eventMatchesShortcut(e, combo('cycle_favorite_model_backward'))
) {
const {
isSettingsDialogOpen,
isCommandPaletteOpen,
isHelpDialogOpen,
isSessionSwitcherOpen,
isAboutDialogOpen,
favoriteModels,
addRecentModel,
} = useUIStore.getState();
if (isSettingsDialogOpen) {
return;
}
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
const isChatActive = true;
if (hasOverlay || !isChatActive || favoriteModels.length === 0) {
return;
}
e.preventDefault();
const { currentProviderId, currentModelId, setProvider, setModel } = useConfigStore.getState();
const len = favoriteModels.length;
const currentIdx = favoriteModels.findIndex(
(f) => f.providerID === currentProviderId && f.modelID === currentModelId,
);
const delta = eventMatchesShortcut(e, combo('cycle_favorite_model_forward')) ? 1 : -1;
const next = favoriteModels[(currentIdx + delta + len) % len];
setProvider(next.providerID);
setModel(next.modelID);
addRecentModel(next.providerID, next.modelID);
return;
}
if (eventMatchesShortcut(e, combo('expand_input'))) {
if (isMobile) {
return;
}
e.preventDefault();
toggleExpandedInput();
return;
}
if (eventMatchesShortcut(e, combo('toggle_dictation'))) {
const { isCommandPaletteOpen, isHelpDialogOpen, isSessionSwitcherOpen, isSettingsDialogOpen } = useUIStore.getState();
if (isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isSettingsDialogOpen) {
return;
}
e.preventDefault();
// Dictation state lives inside the composer's isolated component;
// toggle it via an event instead of subscribing this hot hook to it.
window.dispatchEvent(new CustomEvent('openchamber:dictation-toggle'));
return;
}
};
// Track held physical keys so chord prefixes (e.g. a configured
// `mod+p`) can require their primary key to stay held. Capture phase runs
// before handleKeyDown, so the set is current when chord matching runs.
const handleKeyHoldDown = (e: KeyboardEvent) => {
heldKeysRef.current.add(e.key.toLowerCase());
};
const handleKeyUp = (e: KeyboardEvent) => {
heldKeysRef.current.delete(e.key.toLowerCase());
};
const handleWindowBlur = () => {
heldKeysRef.current.clear();
};
window.addEventListener('keydown', handleKeyHoldDown, true);
window.addEventListener('keyup', handleKeyUp, true);
window.addEventListener('blur', handleWindowBlur);
window.addEventListener('keydown', handleTerminalShortcutCapture, true);
window.addEventListener('keydown', handleEscapeKeyDownCapture, true);
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyHoldDown, true);
window.removeEventListener('keyup', handleKeyUp, true);
window.removeEventListener('blur', handleWindowBlur);
window.removeEventListener('keydown', handleTerminalShortcutCapture, true);
window.removeEventListener('keydown', handleEscapeKeyDownCapture, true);
window.removeEventListener('keydown', handleKeyDown);
};
}, [
openNewSessionDraft,
abortCurrentOperation,
toggleCommandPalette,
toggleHelpDialog,
toggleSidebar,
toggleTerminalSurface,
toggleTerminalSurfaceExpanded,
isMobile,
setSessionSwitcherOpen,
setSettingsDialogOpen,
setModelSelectorOpen,
setTimelineDialogOpen,
togglePromptNavigatorPanel,
setPromptNavigatorPanelOpen,
toggleExpandedInput,
setThemeMode,
sessionPhase,
armAbortPrompt,
resetAbortPriming,
currentSessionId,
currentDirectory,
effectiveDirectory,
activeProject?.id,
activeProject?.path,
shortcutOverrides,
]);
React.useEffect(() => {
return () => {
resetAbortPriming();
};
}, [resetAbortPriming]);
};