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
2 changes: 1 addition & 1 deletion apps/blog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"nextra-theme-docs": "^4.6.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"three": "^0.181.1"
"three": "^0.181.2"
},
"devDependencies": {
"@superset/typescript": "workspace:*",
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@
"lowdb": "^7.0.1",
"meow": "^11.0.0",
"react": "^19.1.1",
"react-devtools-core": "^7.0.1",
"string-width": "^8.1.0"
},
"devDependencies": {
"@sindresorhus/tsconfig": "^3.0.1",
"@superset/typescript": "workspace:*",
"@types/react": "^19.1.11",
"bun-types": "^1.3.1",
"chalk": "^5.6.2",
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"compilerOptions": {
"outDir": "dist",
"noEmit": false,
"allowImportingTsExtensions": false
"allowImportingTsExtensions": false,
"types": ["bun-types"]
},
"include": ["src"]
}
2 changes: 2 additions & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"clsx": "^2.1.1",
"dnd-core": "^16.0.1",
"dotenv": "^17.2.3",
"electron-router-dom": "^2.1.0",
"electron-store": "^11.0.2",
Expand Down Expand Up @@ -92,6 +93,7 @@
"@types/react-syntax-highlighter": "^15.5.13",
"@types/semver": "^7.7.1",
"@vitejs/plugin-react": "^5.0.1",
"bun-types": "^1.3.1",
"code-inspector-plugin": "^1.2.2",
"cross-env": "^10.0.0",
"electron": "39.1.2",
Expand Down
111 changes: 111 additions & 0 deletions apps/desktop/src/renderer/components/HotkeyModal/HotkeyModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@superset/ui/dialog";
import { Kbd, KbdGroup } from "@superset/ui/kbd";
import {
formatKeysForDisplay,
getHotkeysByCategory,
type HotkeyCategory,
type HotkeyDefinition,
} from "shared/hotkeys";

interface HotkeyModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}

const CATEGORY_ORDER: HotkeyCategory[] = [
"Workspace",
"Terminal",
"Layout",
"Window",
"Help",
];

function HotkeyRow({ hotkey }: { hotkey: HotkeyDefinition }) {
const keys = formatKeysForDisplay(hotkey.keys);

return (
<div className="flex items-center justify-between py-1.5">
<span className="text-sm text-foreground">{hotkey.label}</span>
<KbdGroup>
{keys.map((key) => (
<Kbd key={key}>{key}</Kbd>
))}
</KbdGroup>
</div>
);
}

function HotkeySection({
category,
hotkeys,
}: {
category: HotkeyCategory;
hotkeys: HotkeyDefinition[];
}) {
if (hotkeys.length === 0) return null;

// Consolidate workspace jump shortcuts for cleaner display
const consolidatedHotkeys = consolidateWorkspaceJumps(hotkeys);

return (
<div className="space-y-1">
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-2">
{category}
</h3>
{consolidatedHotkeys.map((hotkey) => (
<HotkeyRow key={hotkey.keys} hotkey={hotkey} />
))}
</div>
);
}

/**
* Consolidate individual workspace jump shortcuts (1-9) into a single entry
*/
function consolidateWorkspaceJumps(
hotkeys: HotkeyDefinition[],
): HotkeyDefinition[] {
const workspaceJumpPattern = /^Switch to Workspace \d$/;
const hasWorkspaceJumps = hotkeys.some((h) =>
workspaceJumpPattern.test(h.label),
);

if (!hasWorkspaceJumps) return hotkeys;

const filtered = hotkeys.filter((h) => !workspaceJumpPattern.test(h.label));
filtered.unshift({
keys: "meta+1-9",
label: "Switch to Workspace 1-9",
category: "Workspace",
});

return filtered;
}

export function HotkeyModal({ open, onOpenChange }: HotkeyModalProps) {
const hotkeysByCategory = getHotkeysByCategory();

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-lg max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Keyboard Shortcuts</DialogTitle>
</DialogHeader>
<div className="grid gap-6 py-4">
{CATEGORY_ORDER.map((category) => (
<HotkeySection
key={category}
category={category}
hotkeys={hotkeysByCategory[category]}
/>
))}
</div>
</DialogContent>
</Dialog>
);
}
1 change: 1 addition & 0 deletions apps/desktop/src/renderer/components/HotkeyModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { HotkeyModal } from "./HotkeyModal";
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,24 @@ export function WorkspacesTabs() {
// Flatten workspaces for keyboard navigation
const allWorkspaces = groups.flatMap((group) => group.workspaces);

// Workspace switching shortcuts (work across groups)
useHotkeys("meta+alt+left", () => {
if (!activeWorkspaceId) return;
const index = allWorkspaces.findIndex((w) => w.id === activeWorkspaceId);
if (index > 0) {
setActiveWorkspace.mutate({ id: allWorkspaces[index - 1].id });
}
}, [activeWorkspaceId, allWorkspaces, setActiveWorkspace]);

useHotkeys("meta+alt+right", () => {
if (!activeWorkspaceId) return;
const index = allWorkspaces.findIndex((w) => w.id === activeWorkspaceId);
if (index < allWorkspaces.length - 1) {
setActiveWorkspace.mutate({ id: allWorkspaces[index + 1].id });
}
}, [activeWorkspaceId, allWorkspaces, setActiveWorkspace]);
// Workspace switching shortcuts (⌘+1-9) - combined into single hook call
const workspaceKeys = Array.from(
{ length: 9 },
(_, i) => `meta+${i + 1}`,
).join(", ");
useHotkeys(
workspaceKeys,
(event) => {
const num = Number(event.key);
if (num >= 1 && num <= 9) {
const workspace = allWorkspaces[num - 1];
if (workspace) {
setActiveWorkspace.mutate({ id: workspace.id });
}
}
},
[allWorkspaces, setActiveWorkspace],
);

useEffect(() => {
const checkScroll = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Terminal as XTerm } from "@xterm/xterm";
import { debounce } from "lodash";
import { trpcClient } from "renderer/lib/trpc-client";
import { toXtermTheme } from "renderer/stores/theme/utils";
import { isAppHotkey } from "shared/hotkeys";
import { builtInThemes, DEFAULT_THEME_ID } from "shared/themes";
import { RESIZE_DEBOUNCE_MS, TERMINAL_OPTIONS } from "./config";
import { FilePathLinkProvider } from "./FilePathLinkProvider";
Expand Down Expand Up @@ -106,12 +107,39 @@ export function createTerminalInstance(
// Activate Unicode 11
xterm.unicode.activeVersion = "11";

// Forward app hotkeys to document so useHotkeys can catch them
setupShortcutForwarding(xterm);

// Fit after addons are loaded
fitAddon.fit();

return { xterm, fitAddon };
}

/**
* Setup shortcut forwarding for xterm.
* When an app hotkey is pressed while terminal is focused, re-dispatch to document
* so react-hotkeys-hook handlers can catch it.
*/
function setupShortcutForwarding(xterm: XTerm): void {
xterm.attachCustomKeyEventHandler((event: KeyboardEvent) => {
// Only intercept keydown events with meta/ctrl modifier
if (event.type !== "keydown") return true;
if (!event.metaKey && !event.ctrlKey) return true;

// Check if this is an app hotkey
if (isAppHotkey(event)) {
// Re-dispatch to document for react-hotkeys-hook to catch
document.dispatchEvent(new KeyboardEvent(event.type, event));
// Return false to tell xterm to ignore this event
return false;
}

// Let xterm handle all other keys
return true;
});
}

export function setupFocusListener(
xterm: XTerm,
workspaceId: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
useSetActiveTab,
useTabs,
} from "renderer/stores";
import { HOTKEYS } from "shared/hotkeys";
import { ContentView } from "./ContentView";
import { Sidebar } from "./Sidebar";

Expand All @@ -34,49 +35,36 @@ export function WorkspaceView() {
? activeTabIds[activeWorkspaceId]
: null;

// Tab management shortcuts - work even when sidebar is closed
useHotkeys("meta+t", () => {
// Terminal management shortcuts
useHotkeys(HOTKEYS.NEW_TERMINAL.keys, () => {
if (activeWorkspaceId) {
addTab(activeWorkspaceId);
}
}, [activeWorkspaceId, addTab]);

useHotkeys("meta+w", () => {
useHotkeys(HOTKEYS.CLOSE_TERMINAL.keys, () => {
if (activeTabId) {
removeTab(activeTabId);
}
}, [activeTabId, removeTab]);

useHotkeys("meta+alt+up", () => {
// Switch between visible terminal panes (⌘+Up/Down)
useHotkeys(HOTKEYS.PREV_TERMINAL.keys, () => {
if (!activeWorkspaceId || !activeTabId) return;
const index = tabs.findIndex((t) => t.id === activeTabId);
if (index > 0) {
setActiveTab(activeWorkspaceId, tabs[index - 1].id);
}
}, [activeWorkspaceId, activeTabId, tabs, setActiveTab]);

useHotkeys("meta+alt+down", () => {
useHotkeys(HOTKEYS.NEXT_TERMINAL.keys, () => {
if (!activeWorkspaceId || !activeTabId) return;
const index = tabs.findIndex((t) => t.id === activeTabId);
if (index < tabs.length - 1) {
setActiveTab(activeWorkspaceId, tabs[index + 1].id);
}
}, [activeWorkspaceId, activeTabId, tabs, setActiveTab]);

// Jump to tab by number (Cmd+1 through Cmd+9)
useHotkeys(
"meta+1,meta+2,meta+3,meta+4,meta+5,meta+6,meta+7,meta+8,meta+9",
(_, handler) => {
if (!activeWorkspaceId) return;
const key = handler.keys?.join("");
const num = key ? Number.parseInt(key, 10) : null;
if (num && tabs[num - 1]) {
setActiveTab(activeWorkspaceId, tabs[num - 1].id);
}
},
[activeWorkspaceId, tabs, setActiveTab],
);

return (
<div className="flex flex-1 bg-tertiary">
<Sidebar />
Expand Down
16 changes: 11 additions & 5 deletions apps/desktop/src/renderer/screens/main/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useState } from "react";
import { DndProvider } from "react-dnd";
import { useHotkeys } from "react-hotkeys-hook";
import { HotkeyModal } from "renderer/components/HotkeyModal";
import { trpc } from "renderer/lib/trpc";
import { useCurrentView } from "renderer/stores/app-state";
import { useSidebarStore } from "renderer/stores/sidebar-state";
Expand All @@ -8,6 +10,7 @@ import {
useSplitTabHorizontal,
useSplitTabVertical,
} from "renderer/stores/tabs";
import { HOTKEYS } from "shared/hotkeys";
import { dragDropManager } from "../../lib/dnd";
import { AppFrame } from "./components/AppFrame";
import { Background } from "./components/Background";
Expand All @@ -21,26 +24,28 @@ export function MainScreen() {
const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery();
const splitTabVertical = useSplitTabVertical();
const splitTabHorizontal = useSplitTabHorizontal();
const [hotkeyModalOpen, setHotkeyModalOpen] = useState(false);

// Listen for agent completion hooks from main process
useAgentHookListener();

const activeWorkspaceId = activeWorkspace?.id;
const isWorkspaceView = currentView === "workspace";

// Sidebar toggle shortcut - only in workspace view
useHotkeys("meta+s", () => {
// Register global shortcuts
useHotkeys(HOTKEYS.SHOW_HOTKEYS.keys, () => setHotkeyModalOpen(true), []);

useHotkeys(HOTKEYS.TOGGLE_SIDEBAR.keys, () => {
if (isWorkspaceView) toggleSidebar();
}, [toggleSidebar, isWorkspaceView]);

// Split view shortcuts - only in workspace view
useHotkeys("meta+d", () => {
useHotkeys(HOTKEYS.SPLIT_HORIZONTAL.keys, () => {
if (isWorkspaceView && activeWorkspaceId) {
splitTabVertical(activeWorkspaceId);
}
}, [activeWorkspaceId, splitTabVertical, isWorkspaceView]);

useHotkeys("meta+shift+d", () => {
useHotkeys(HOTKEYS.SPLIT_VERTICAL.keys, () => {
if (isWorkspaceView && activeWorkspaceId) {
splitTabHorizontal(activeWorkspaceId);
}
Expand All @@ -57,6 +62,7 @@ export function MainScreen() {
</div>
</div>
</AppFrame>
<HotkeyModal open={hotkeyModalOpen} onOpenChange={setHotkeyModalOpen} />
</DndProvider>
);
}
Loading