feat(ui): add command palette and commit selection sync

Introduce a global command palette for quick access to common repository
actions, branches, files, and commits. Wire it into the main shell so it
can open settings, help, and other dialogs while keeping keyboard access
consistent.

Also add commit selection state to the history view so the active commit
is highlighted and brought into view when chosen from either the palette
or the history panel.
This commit is contained in:
Christoph Brandau
2026-08-13 07:53:19 +02:00
parent 1fa57eea6f
commit 3eb554fee7
4 changed files with 245 additions and 2 deletions
+70 -2
View File
@@ -17,6 +17,7 @@
import BranchDeleteConfirmDialog from "./lib/components/BranchDeleteConfirmDialog.svelte";
import BranchPanel from "./lib/components/BranchPanel.svelte";
import CloneRepositoryDialog from "./lib/components/CloneRepositoryDialog.svelte";
import CommandPalette from "./lib/components/CommandPalette.svelte";
import CommitPanel from "./lib/components/CommitPanel.svelte";
import CompareSelectDialog from "./lib/components/CompareSelectDialog.svelte";
import CredentialDialog from "./lib/components/CredentialDialog.svelte";
@@ -287,6 +288,7 @@
let selectedExplorerKind: ExplorerNodeKind = "file";
let expandedExplorerPaths = new Set<string>();
let expandedCommitHashes = new Set<string>();
let selectedCommitHash = "";
let fileHistory: GitCommit[] = [];
let fileHistoryLoading = false;
let fileHistoryError = "";
@@ -312,6 +314,7 @@
let aiSettingsOpen = false;
let appSettingsOpen = false;
let helpOpen = false;
let commandPaletteOpen = false;
let analyticsNoticeOpen = false;
let analyticsSettings: AnalyticsSettings = defaultAnalyticsSettings();
let appTheme: AppTheme = loadThemePreference();
@@ -3749,6 +3752,24 @@
}
}
async function openFileFromCommandPalette(file: GitRepositoryFile) {
if (!activeRepoPath) return;
selectedExplorerPath = file.path;
selectedExplorerKind = "file";
expandedExplorerPaths = new Set([...expandedExplorerPaths, ...explorerParentFolders(file.path)]);
try {
await openRepositoryFile(activeRepoPath, file.path);
trackEvent("explorer_file_opened", { source: "command_palette", tracked: file.tracked ? 1 : 0 });
} catch (error) {
errorMessage = errorToMessage(error);
}
}
function selectCommitFromCommandPalette(target: GitCommit) {
selectedCommitHash = target.hash;
trackEvent("commit_selected", { source: "command_palette" });
}
async function restoreSelectedFileFromCommit(target: GitCommit) {
if (!activeRepoPath || !selectedExplorerPath) return;
const kind = selectedExplorerKind === "folder" ? "folder" : "file";
@@ -3781,6 +3802,14 @@
trackEvent("help_opened");
}
function openAppSettings() {
appSettingsOpen = true;
}
function openAiSettings() {
aiSettingsOpen = true;
}
async function compareSelectedCommits() {
if (!canCompare) return;
await runOperation("Comparing commits", async () => {
@@ -3970,11 +3999,20 @@
// ── Event handlers ─────────────────────────────────────────────────────────
function handleWindowKeydown(event: KeyboardEvent) {
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
event.preventDefault();
if (!event.repeat) commandPaletteOpen = !commandPaletteOpen;
return;
}
if ((event.ctrlKey || event.metaKey) && event.key === "/") {
event.preventDefault();
openHelp();
return;
}
if (event.key === "Escape" && commandPaletteOpen) {
commandPaletteOpen = false;
return;
}
if (event.key === "Escape" && helpOpen) {
helpOpen = false;
return;
@@ -4012,7 +4050,7 @@
<main class="shell">
<TitleBar
onOpenSettings={() => { appSettingsOpen = true; }}
onOpenSettings={openAppSettings}
onOpenHelp={openHelp}
language={appLanguage}
/>
@@ -4544,7 +4582,7 @@
onGenerateCommitMessage={generateCommitMessageWithAi}
onReviewStaged={reviewStagedWithAi}
onSplitStaged={splitStagedWithAi}
onOpenAiSettings={() => { aiSettingsOpen = true; }}
onOpenAiSettings={openAiSettings}
onToggleAmend={toggleAmendMode}
onUndoLastCommit={undoLastCommitChange}
/>
@@ -4574,6 +4612,7 @@
<aside class="history-aside" aria-label="Commit history">
<HistoryPanel
{commits}
{selectedCommitHash}
{localBranchNames}
activeBranch={status?.current_branch ?? ""}
activeUpstream={status?.upstream ?? ""}
@@ -4590,6 +4629,7 @@
onCreateBranchFromCommit={openNewBranchDialog}
onCherryPickCommit={cherryPickFromCommit}
onRevertCommit={revertHistoryCommit}
onSelectCommit={(commit) => { selectedCommitHash = commit.hash; }}
onToggleCommitFiles={(hash) => {
const next = new Set(expandedCommitHashes);
if (next.has(hash)) next.delete(hash); else next.add(hash);
@@ -4615,6 +4655,34 @@
</div>
</main>
{#if commandPaletteOpen}
<CommandPalette
language={appLanguage}
{hasRepository}
{isBusy}
{branches}
files={repoFiles}
{commits}
onClose={() => { commandPaletteOpen = false; }}
onCheckoutBranch={checkout}
onOpenFile={openFileFromCommandPalette}
onSelectCommit={selectCommitFromCommandPalette}
onFetch={fetchRepo}
onPull={pullRepo}
onPush={pushRepo}
onRefresh={refreshRepo}
onOpenSearch={openGlobalSearchDialog}
onOpenCompare={openCompareSelect}
onOpenReflog={openReflog}
onOpenInteractiveRebase={openInteractiveRebase}
onOpenWorktrees={openWorktreeDialog}
onOpenSyncSettings={openSyncOptions}
onOpenSettings={openAppSettings}
onOpenAiSettings={openAiSettings}
onOpenHelp={openHelp}
/>
{/if}
{#if updateToastOpen}
<UpdateToast
state={updateToastState}