diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index f64eb142d6f..10f550ccfe0 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -64,6 +64,7 @@
"line-column-path": "^3.0.0",
"lodash": "^4.17.21",
"lowdb": "^7.0.1",
+ "lucide-react": "^0.555.0",
"nanoid": "^5.1.6",
"node-pty": "1.1.0-beta30",
"react": "^19.1.1",
diff --git a/apps/desktop/src/lib/trpc/routers/projects/projects.ts b/apps/desktop/src/lib/trpc/routers/projects/projects.ts
index d8a51bbe3c8..ca60519ef2e 100644
--- a/apps/desktop/src/lib/trpc/routers/projects/projects.ts
+++ b/apps/desktop/src/lib/trpc/routers/projects/projects.ts
@@ -1,15 +1,85 @@
-import { basename } from "node:path";
+import { existsSync } from "node:fs";
+import { access } from "node:fs/promises";
+import { basename, join } from "node:path";
import type { BrowserWindow } from "electron";
import { dialog } from "electron";
import { db } from "main/lib/db";
import type { Project } from "main/lib/db/schemas";
import { nanoid } from "nanoid";
import { PROJECT_COLOR_VALUES } from "shared/constants/project-colors";
+import simpleGit from "simple-git";
import { z } from "zod";
import { publicProcedure, router } from "../..";
import { getGitRoot } from "../workspaces/utils/git";
import { assignRandomColor } from "./utils/colors";
+// Safe filename regex: letters, numbers, dots, underscores, hyphens, spaces, and common unicode
+// Allows most valid Git repo names while avoiding path traversal characters
+const SAFE_REPO_NAME_REGEX = /^[a-zA-Z0-9._\- ]+$/;
+
+/**
+ * Extracts and validates a repository name from a git URL.
+ * Handles HTTP/HTTPS URLs, SSH-style URLs (git@host:user/repo), and edge cases.
+ */
+function extractRepoName(urlInput: string): string | null {
+ // Normalize: trim whitespace and strip trailing slashes
+ let normalized = urlInput.trim().replace(/\/+$/, "");
+
+ if (!normalized) return null;
+
+ let repoSegment: string | undefined;
+
+ // Try parsing as HTTP/HTTPS URL first
+ try {
+ const parsed = new URL(normalized);
+ if (parsed.protocol === "http:" || parsed.protocol === "https:") {
+ // Get pathname and strip query/hash (URL constructor handles this)
+ const pathname = parsed.pathname;
+ // Get the last segment of the path
+ repoSegment = pathname.split("/").filter(Boolean).pop();
+ }
+ } catch {
+ // Not a valid URL, try SSH-style parsing
+ }
+
+ // Fallback to SSH-style parsing (git@github.com:user/repo.git)
+ if (!repoSegment) {
+ // Handle SSH format: git@host:path or just path segments
+ const colonIndex = normalized.indexOf(":");
+ if (colonIndex !== -1 && !normalized.includes("://")) {
+ // SSH-style: take everything after the colon
+ normalized = normalized.slice(colonIndex + 1);
+ }
+ // Split by '/' and get the last segment
+ repoSegment = normalized.split("/").filter(Boolean).pop();
+ }
+
+ if (!repoSegment) return null;
+
+ // Strip query string and hash if present (for edge cases)
+ repoSegment = repoSegment.split("?")[0].split("#")[0];
+
+ // Remove trailing .git extension
+ repoSegment = repoSegment.replace(/\.git$/, "");
+
+ // Decode percent-encoded characters
+ try {
+ repoSegment = decodeURIComponent(repoSegment);
+ } catch {
+ // Invalid encoding, continue with raw value
+ }
+
+ // Trim any remaining whitespace or special characters at boundaries
+ repoSegment = repoSegment.trim();
+
+ // Validate against safe filename regex
+ if (!repoSegment || !SAFE_REPO_NAME_REGEX.test(repoSegment)) {
+ return null;
+ }
+
+ return repoSegment;
+}
+
export const createProjectsRouter = (window: BrowserWindow) => {
return router({
getRecents: publicProcedure.query((): Project[] => {
@@ -73,6 +143,129 @@ export const createProjectsRouter = (window: BrowserWindow) => {
};
}),
+ cloneRepo: publicProcedure
+ .input(
+ z.object({
+ url: z.string().url(),
+ // Trim and convert empty/whitespace strings to undefined
+ targetDirectory: z
+ .string()
+ .trim()
+ .optional()
+ .transform((v) => (v && v.length > 0 ? v : undefined)),
+ }),
+ )
+ .mutation(async ({ input }) => {
+ try {
+ let targetDir = input.targetDirectory;
+
+ if (!targetDir) {
+ const result = await dialog.showOpenDialog(window, {
+ properties: ["openDirectory", "createDirectory"],
+ title: "Select Clone Destination",
+ });
+
+ // User canceled - return canceled state (not an error)
+ if (result.canceled || result.filePaths.length === 0) {
+ return { canceled: true as const, success: false as const };
+ }
+
+ targetDir = result.filePaths[0];
+ }
+
+ const repoName = extractRepoName(input.url);
+ if (!repoName) {
+ return {
+ canceled: false as const,
+ success: false as const,
+ error: "Invalid repository URL",
+ };
+ }
+
+ const clonePath = join(targetDir, repoName);
+
+ // Check if we already have a project for this path
+ const existingProject = db.data.projects.find(
+ (p) => p.mainRepoPath === clonePath,
+ );
+
+ if (existingProject) {
+ // Verify the filesystem path still exists
+ try {
+ await access(clonePath);
+ // Directory exists - update lastOpenedAt and return existing project
+ await db.update((data) => {
+ const p = data.projects.find(
+ (p) => p.id === existingProject.id,
+ );
+ if (p) {
+ p.lastOpenedAt = Date.now();
+ }
+ });
+ return {
+ canceled: false as const,
+ success: true as const,
+ project: existingProject,
+ };
+ } catch {
+ // Directory is missing - remove the stale project record and continue with clone
+ await db.update((data) => {
+ const index = data.projects.findIndex(
+ (p) => p.id === existingProject.id,
+ );
+ if (index !== -1) {
+ data.projects.splice(index, 1);
+ }
+ });
+ // Continue to normal creation flow below
+ }
+ }
+
+ // Check if target directory already exists (but not our project)
+ if (existsSync(clonePath)) {
+ return {
+ canceled: false as const,
+ success: false as const,
+ error: `A folder named "${repoName}" already exists at this location. Please choose a different destination.`,
+ };
+ }
+
+ // Clone the repository
+ const git = simpleGit();
+ await git.clone(input.url, clonePath);
+
+ // Create new project
+ const name = basename(clonePath);
+ const project: Project = {
+ id: nanoid(),
+ mainRepoPath: clonePath,
+ name,
+ color: assignRandomColor(),
+ tabOrder: null,
+ lastOpenedAt: Date.now(),
+ createdAt: Date.now(),
+ };
+
+ await db.update((data) => {
+ data.projects.push(project);
+ });
+
+ return {
+ canceled: false as const,
+ success: true as const,
+ project,
+ };
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ return {
+ canceled: false as const,
+ success: false as const,
+ error: `Failed to clone repository: ${errorMessage}`,
+ };
+ }
+ }),
+
update: publicProcedure
.input(
z.object({
diff --git a/apps/desktop/src/lib/trpc/routers/window.ts b/apps/desktop/src/lib/trpc/routers/window.ts
index dc19ec73965..2c4d4fe5ee2 100644
--- a/apps/desktop/src/lib/trpc/routers/window.ts
+++ b/apps/desktop/src/lib/trpc/routers/window.ts
@@ -1,3 +1,4 @@
+import { homedir } from "node:os";
import type { BrowserWindow } from "electron";
import { publicProcedure, router } from "..";
@@ -33,6 +34,10 @@ export const createWindowRouter = (window: BrowserWindow) => {
getPlatform: publicProcedure.query(() => {
return process.platform;
}),
+
+ getHomeDir: publicProcedure.query(() => {
+ return homedir();
+ }),
});
};
diff --git a/apps/desktop/src/main/windows/main.ts b/apps/desktop/src/main/windows/main.ts
index 867494fdc06..43c760ab6ec 100644
--- a/apps/desktop/src/main/windows/main.ts
+++ b/apps/desktop/src/main/windows/main.ts
@@ -20,6 +20,8 @@ export async function MainWindow() {
title: productName,
width,
height,
+ minWidth: 400,
+ minHeight: 400,
show: false,
center: true,
movable: true,
diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/KeyboardShortcutsSettings.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/KeyboardShortcutsSettings.tsx
new file mode 100644
index 00000000000..fc9fa2c6168
--- /dev/null
+++ b/apps/desktop/src/renderer/screens/main/components/SettingsView/KeyboardShortcutsSettings.tsx
@@ -0,0 +1,149 @@
+import { Input } from "@superset/ui/input";
+import { Kbd, KbdGroup } from "@superset/ui/kbd";
+import { useMemo, useState } from "react";
+import { HiMagnifyingGlass } from "react-icons/hi2";
+import {
+ formatKeysForDisplay,
+ getHotkeysByCategory,
+ type HotkeyCategory,
+ type HotkeyDefinition,
+} from "shared/hotkeys";
+
+function useIsMac(): boolean {
+ return useMemo(() => {
+ const platform = navigator.platform?.toUpperCase() ?? "";
+ const userAgent = navigator.userAgent?.toUpperCase() ?? "";
+ return platform.includes("MAC") || userAgent.includes("MAC");
+ }, []);
+}
+
+const CATEGORY_ORDER: HotkeyCategory[] = [
+ "Workspace",
+ "Terminal",
+ "Layout",
+ "Window",
+ "Help",
+];
+
+function HotkeyRow({
+ hotkey,
+ isEven,
+}: {
+ hotkey: HotkeyDefinition;
+ isEven: boolean;
+}) {
+ const keys = formatKeysForDisplay(hotkey.keys);
+
+ return (
+
+ {hotkey.label}
+
+ {keys.map((key) => (
+ {key}
+ ))}
+
+
+ );
+}
+
+/**
+ * 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 KeyboardShortcutsSettings() {
+ const [searchQuery, setSearchQuery] = useState("");
+ const hotkeysByCategory = getHotkeysByCategory();
+ const isMac = useIsMac();
+ const modifierKey = isMac ? "⌘" : "Ctrl";
+
+ // Flatten and consolidate all hotkeys
+ const allHotkeys = CATEGORY_ORDER.flatMap((category) =>
+ consolidateWorkspaceJumps(hotkeysByCategory[category]),
+ );
+
+ // Filter based on search query
+ const filteredHotkeys = searchQuery
+ ? allHotkeys.filter((hotkey) =>
+ hotkey.label.toLowerCase().includes(searchQuery.toLowerCase()),
+ )
+ : allHotkeys;
+
+ return (
+
+ {/* Header */}
+
+
Keyboard Shortcuts
+
+ View all available keyboard shortcuts. Press{" "}
+ {modifierKey}
+ ? to open this page anytime.
+
+
+
+ {/* Search */}
+
+
+ setSearchQuery(e.target.value)}
+ className="pl-9 bg-accent/30 border-transparent focus:border-accent"
+ />
+
+
+ {/* Table */}
+
+ {/* Table Header */}
+
+
+ Command
+
+
+ Shortcut
+
+
+
+ {/* Table Body */}
+
+ {filteredHotkeys.length > 0 ? (
+ filteredHotkeys.map((hotkey, index) => (
+
+ ))
+ ) : (
+
+ No shortcuts found matching "{searchQuery}"
+
+ )}
+
+
+
+ );
+}
diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsContent.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsContent.tsx
index a3d4e4f4700..812e0b84eeb 100644
--- a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsContent.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsContent.tsx
@@ -1,5 +1,6 @@
+import type { SettingsSection } from "renderer/stores";
import { AppearanceSettings } from "./AppearanceSettings";
-import type { SettingsSection } from "./index";
+import { KeyboardShortcutsSettings } from "./KeyboardShortcutsSettings";
interface SettingsContentProps {
activeSection: SettingsSection;
@@ -7,8 +8,9 @@ interface SettingsContentProps {
export function SettingsContent({ activeSection }: SettingsContentProps) {
return (
-
+
{activeSection === "appearance" &&
}
+ {activeSection === "keyboard" &&
}
);
}
diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar.tsx
index b660e168f78..0bde8d42e11 100644
--- a/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/SettingsView/SettingsSidebar.tsx
@@ -1,7 +1,10 @@
import { cn } from "@superset/ui/utils";
-import { HiArrowLeft, HiOutlinePaintBrush } from "react-icons/hi2";
-import { useCloseSettings } from "renderer/stores";
-import type { SettingsSection } from "./index";
+import {
+ HiArrowLeft,
+ HiOutlineCommandLine,
+ HiOutlinePaintBrush,
+} from "react-icons/hi2";
+import { type SettingsSection, useCloseSettings } from "renderer/stores";
interface SettingsSidebarProps {
activeSection: SettingsSection;
@@ -18,6 +21,11 @@ const SECTIONS: {
label: "Appearance",
icon:
,
},
+ {
+ id: "keyboard",
+ label: "Keyboard Shortcuts",
+ icon:
,
+ },
];
export function SettingsSidebar({
diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/index.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/index.tsx
index 1e176c03f44..69f02869f33 100644
--- a/apps/desktop/src/renderer/screens/main/components/SettingsView/index.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/SettingsView/index.tsx
@@ -1,12 +1,13 @@
-import { useState } from "react";
+import {
+ useSetSettingsSection,
+ useSettingsSection,
+} from "renderer/stores/app-state";
import { SettingsContent } from "./SettingsContent";
import { SettingsSidebar } from "./SettingsSidebar";
-export type SettingsSection = "appearance";
-
export function SettingsView() {
- const [activeSection, setActiveSection] =
- useState
("appearance");
+ const activeSection = useSettingsSection();
+ const setActiveSection = useSetSettingsSection();
return (
diff --git a/apps/desktop/src/renderer/screens/main/components/StartView/ActionCard.tsx b/apps/desktop/src/renderer/screens/main/components/StartView/ActionCard.tsx
new file mode 100644
index 00000000000..0510eb65dc5
--- /dev/null
+++ b/apps/desktop/src/renderer/screens/main/components/StartView/ActionCard.tsx
@@ -0,0 +1,39 @@
+import type { LucideIcon } from "lucide-react";
+
+interface ActionCardProps {
+ icon: LucideIcon;
+ label: string;
+ onClick?: () => void;
+ disabled?: boolean;
+ isLoading?: boolean;
+}
+
+export function ActionCard({
+ icon: Icon,
+ label,
+ onClick,
+ disabled = false,
+ isLoading = false,
+}: ActionCardProps) {
+ return (
+
+ );
+}
diff --git a/apps/desktop/src/renderer/screens/main/components/StartView/CloneRepoDialog.tsx b/apps/desktop/src/renderer/screens/main/components/StartView/CloneRepoDialog.tsx
new file mode 100644
index 00000000000..2c0deffab63
--- /dev/null
+++ b/apps/desktop/src/renderer/screens/main/components/StartView/CloneRepoDialog.tsx
@@ -0,0 +1,111 @@
+import { useState } from "react";
+import { trpc } from "renderer/lib/trpc";
+import { useCreateWorkspace } from "renderer/react-query/workspaces";
+
+interface CloneRepoDialogProps {
+ isOpen: boolean;
+ onClose: () => void;
+ onError: (error: string) => void;
+}
+
+export function CloneRepoDialog({
+ isOpen,
+ onClose,
+ onError,
+}: CloneRepoDialogProps) {
+ const [url, setUrl] = useState("");
+ const utils = trpc.useUtils();
+ const cloneRepo = trpc.projects.cloneRepo.useMutation();
+ const createWorkspace = useCreateWorkspace();
+
+ const handleClone = async () => {
+ if (!url.trim()) {
+ onError("Please enter a repository URL");
+ return;
+ }
+
+ cloneRepo.mutate(
+ { url: url.trim() },
+ {
+ onSuccess: (result) => {
+ // User canceled the directory picker - silent no-op
+ if (result.canceled) {
+ return;
+ }
+
+ if (result.success && result.project) {
+ // Invalidate recents so the new/updated project appears
+ utils.projects.getRecents.invalidate();
+ createWorkspace.mutate({ projectId: result.project.id });
+ onClose();
+ setUrl("");
+ } else if (!result.success) {
+ // Show user-friendly error message
+ onError(result.error ?? "Failed to clone repository");
+ }
+ },
+ onError: (err) => {
+ onError(err.message || "Failed to clone repository");
+ },
+ },
+ );
+ };
+
+ if (!isOpen) return null;
+
+ const isLoading = cloneRepo.isPending || createWorkspace.isPending;
+
+ return (
+
+
+
+ Clone Repository
+
+
+
+
+
+ setUrl(e.target.value)}
+ placeholder="https://github.com/user/repo.git"
+ className="w-full px-3 py-2.5 bg-background border border-border rounded-md text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:border-ring transition-colors"
+ disabled={isLoading}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && !isLoading) {
+ handleClone();
+ }
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/desktop/src/renderer/screens/main/components/StartView/StartTopBar.tsx b/apps/desktop/src/renderer/screens/main/components/StartView/StartTopBar.tsx
new file mode 100644
index 00000000000..140c1ad564c
--- /dev/null
+++ b/apps/desktop/src/renderer/screens/main/components/StartView/StartTopBar.tsx
@@ -0,0 +1,29 @@
+import { trpc } from "renderer/lib/trpc";
+import { SettingsButton } from "../TopBar/SettingsButton";
+import { WindowControls } from "../TopBar/WindowControls";
+
+export function StartTopBar() {
+ const { data: platform, isLoading } = trpc.window.getPlatform.useQuery();
+ const isMac = !isLoading && platform === "darwin";
+ const showWindowControls = !isLoading && !isMac;
+
+ return (
+
+
+ {/* Empty space on left for symmetry */}
+
+
+ {/* Empty middle section - no tabs */}
+
+
+
+ {showWindowControls && }
+
+
+ );
+}
diff --git a/apps/desktop/src/renderer/screens/main/components/StartView/index.tsx b/apps/desktop/src/renderer/screens/main/components/StartView/index.tsx
new file mode 100644
index 00000000000..d5ea4d96271
--- /dev/null
+++ b/apps/desktop/src/renderer/screens/main/components/StartView/index.tsx
@@ -0,0 +1,253 @@
+import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
+import { ChevronUp, FolderGit, FolderOpen, X } from "lucide-react";
+import { useState } from "react";
+import { HiExclamationTriangle } from "react-icons/hi2";
+import { trpc } from "renderer/lib/trpc";
+import { useOpenNew } from "renderer/react-query/projects";
+import { useCreateWorkspace } from "renderer/react-query/workspaces";
+import { ActionCard } from "./ActionCard";
+import { CloneRepoDialog } from "./CloneRepoDialog";
+import { StartTopBar } from "./StartTopBar";
+
+/**
+ * Normalizes path separators to forward slashes for consistent handling
+ */
+function normalizeSeparators(path: string): string {
+ return path.replace(/\\/g, "/");
+}
+
+/**
+ * Formats a path for display, replacing the home directory with ~ and optionally
+ * removing the trailing project name directory.
+ * Handles both Unix and Windows paths.
+ */
+function formatPath(
+ path: string,
+ projectName: string,
+ homeDir: string | undefined,
+): { display: string; full: string } {
+ // Normalize both path and homeDir to use forward slashes
+ const normalizedPath = normalizeSeparators(path);
+ const normalizedHome = homeDir ? normalizeSeparators(homeDir) : null;
+
+ // Replace home directory with ~ if we know the home dir
+ let fullPath = normalizedPath;
+ if (normalizedHome && normalizedPath.startsWith(normalizedHome)) {
+ fullPath = `~${normalizedPath.slice(normalizedHome.length)}`;
+ } else {
+ // Fallback: try common Unix patterns if home dir not available
+ fullPath = normalizedPath.replace(/^\/(?:Users|home)\/[^/]+/, "~");
+ }
+
+ // Escape special regex characters in project name
+ const escapedProjectName = projectName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ const suffixPattern = new RegExp(`/${escapedProjectName}$`);
+
+ // Remove trailing project name directory if it matches
+ const displayPath = fullPath.replace(suffixPattern, "");
+
+ return { display: displayPath, full: fullPath };
+}
+
+export function StartView() {
+ const { data: recentProjects = [] } = trpc.projects.getRecents.useQuery();
+ const { data: homeDir } = trpc.window.getHomeDir.useQuery();
+ const openNew = useOpenNew();
+ const createWorkspace = useCreateWorkspace();
+ const [error, setError] = useState
(null);
+ const [isCloneDialogOpen, setIsCloneDialogOpen] = useState(false);
+ const [showAllProjects, setShowAllProjects] = useState(false);
+ const [visibleCount, setVisibleCount] = useState(50);
+
+ const handleOpenProject = () => {
+ setError(null);
+ openNew.mutate(undefined, {
+ onSuccess: (result) => {
+ if (!result.canceled && result.project) {
+ createWorkspace.mutate({ projectId: result.project.id });
+ }
+ },
+ onError: (err) => {
+ setError(err.message || "Failed to open project");
+ },
+ });
+ };
+
+ const handleOpenRecentProject = (projectId: string) => {
+ setError(null);
+ createWorkspace.mutate(
+ { projectId },
+ {
+ onError: (err) => {
+ setError(err.message || "Failed to create workspace");
+ },
+ },
+ );
+ };
+
+ const hasMoreProjects = recentProjects.length > 5;
+ const displayedProjects = showAllProjects
+ ? recentProjects.slice(0, visibleCount)
+ : recentProjects.slice(0, 5);
+ const hasMoreToLoad = showAllProjects && recentProjects.length > visibleCount;
+ const isLoading = openNew.isPending || createWorkspace.isPending;
+
+ return (
+
+
+
+
+ {/* Logo */}
+
+
+ {/* Error Display */}
+ {error && (
+
+
+
+
+
+
{error}
+
+
+
+ )}
+
+ {/* Action Cards and Recent Projects Container */}
+
+ {/* Action Cards */}
+
+
+
+
{
+ setError(null);
+ setIsCloneDialogOpen(true);
+ }}
+ isLoading={isLoading}
+ />
+
+
+ {/* Recent Projects */}
+ {displayedProjects.length > 0 && (
+
+
+
+
+ Recent projects
+
+ {hasMoreProjects && (
+
+ )}
+
+
+
+ {displayedProjects.map((project) => {
+ const pathInfo = formatPath(
+ project.mainRepoPath,
+ project.name,
+ homeDir,
+ );
+ return (
+
+ );
+ })}
+
+ {hasMoreToLoad && (
+
+ )}
+
+
+
+ )}
+
+
+
+
+ {/* Dialogs */}
+
setIsCloneDialogOpen(false)}
+ onError={setError}
+ />
+
+ );
+}
diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/SettingsButton/SettingsButton.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/SettingsButton/SettingsButton.tsx
index 86265ae163b..d3dbacd3ff4 100644
--- a/apps/desktop/src/renderer/screens/main/components/TopBar/SettingsButton/SettingsButton.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/TopBar/SettingsButton/SettingsButton.tsx
@@ -7,7 +7,7 @@ export function SettingsButton() {
return (