{tabs.map((tab) => {
const isActive = activeTab === tab.id;
@@ -38,6 +38,7 @@ export function SidebarHeader({
getSidebarHeaderTabButtonClassName({
isActive,
compact,
+ inverted: true,
}),
"relative flex-1 justify-center",
)}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx
index 55b3973ac05..58077a6f823 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx
@@ -1,3 +1,4 @@
+import { OverflowFadeContainer } from "@superset/ui/overflow-fade-container";
import { memo, useMemo } from "react";
import type { ChangesetFile } from "renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useChangeset";
import type { ChangesViewMode } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema";
@@ -86,7 +87,8 @@ export const ChangesFileList = memo(function ChangesFileList({
}
return (
-
@@ -132,6 +134,6 @@ export const ChangesFileList = memo(function ChangesFileList({
);
})}
-
+
);
});
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesHeader/components/ViewModeToggle/ViewModeToggle.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesHeader/components/ViewModeToggle/ViewModeToggle.tsx
index 0ca41b1022b..fcfdd888f38 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesHeader/components/ViewModeToggle/ViewModeToggle.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesHeader/components/ViewModeToggle/ViewModeToggle.tsx
@@ -1,5 +1,5 @@
+import { Button } from "@superset/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
-import { cn } from "@superset/ui/utils";
import { Folder, ListTree } from "lucide-react";
import type { ChangesViewMode } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema";
@@ -9,58 +9,25 @@ interface ViewModeToggleProps {
}
/**
- * Two-button segmented toggle: folders (flat by parent folder) vs tree
- * (full directory hierarchy).
+ * Single toggle between folders (flat by parent folder) and tree (full
+ * directory hierarchy) views. Shows the mode it will switch to.
*/
export function ViewModeToggle({ viewMode, onChange }: ViewModeToggleProps) {
- return (
-
- onChange("folders")}
- />
- onChange("tree")}
- />
-
- );
-}
-
-interface ToggleButtonProps {
- icon: React.ComponentType<{ className?: string }>;
- label: string;
- active: boolean;
- onClick: () => void;
-}
-
-function ToggleButton({
- icon: Icon,
- label,
- active,
- onClick,
-}: ToggleButtonProps) {
+ const next: ChangesViewMode = viewMode === "folders" ? "tree" : "folders";
+ const label = next === "tree" ? "Tree view" : "Folder view";
+ const Icon = next === "tree" ? ListTree : Folder;
return (
-
+
+
{label}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx
index e9f18624a26..aa7018c0a5d 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx
@@ -111,32 +111,34 @@ export const ChangesTabContent = memo(function ChangesTabContent({
return (
-
-
+
+
+
+
-
+ <>
+
)}
- {isRefreshing && (
-
-
-
- )}
-
-
-
-
-
-
- Refresh changes
-
-
-
-
-
- {label}
-
+
+
+
+
+
+
+
+ Refresh changes
+
+
+
+
+
+ {label}
+
+
-
+ >
);
}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalPaneHeaderExtras/TerminalPaneHeaderExtras.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalPaneHeaderExtras/TerminalPaneHeaderExtras.tsx
index 57f8b6f6dfa..6c730cc1a27 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalPaneHeaderExtras/TerminalPaneHeaderExtras.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalPaneHeaderExtras/TerminalPaneHeaderExtras.tsx
@@ -55,10 +55,6 @@ export function TerminalPaneHeaderExtras({
{label}
-
);
}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalSessionDropdown/TerminalSessionDropdown.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalSessionDropdown/TerminalSessionDropdown.tsx
index 2a79526ab5e..34d4f35ff93 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalSessionDropdown/TerminalSessionDropdown.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalSessionDropdown/TerminalSessionDropdown.tsx
@@ -11,7 +11,7 @@ import { toast } from "@superset/ui/sonner";
import { workspaceTrpc } from "@superset/workspace-client";
import { eq } from "@tanstack/db";
import { useLiveQuery } from "@tanstack/react-db";
-import { Check, ChevronDown, LoaderCircle, Plus, Trash2 } from "lucide-react";
+import { Check, LoaderCircle, Plus, Trash2 } from "lucide-react";
import { useCallback, useMemo, useState, useSyncExternalStore } from "react";
import { useRenderStressInstrumentation } from "renderer/lib/performance/stress-instrumentation";
import { markTerminalForBackground } from "renderer/lib/terminal/terminal-background-intents";
@@ -310,10 +310,8 @@ export function TerminalSessionDropdown({
{triggerTitle}
- {sessionsQuery.isFetching && isOpen ? (
+ {sessionsQuery.isFetching && isOpen && (
- ) : (
-
)}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
index f9f3e0c3e5b..dde404e0765 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
@@ -6,6 +6,9 @@ import { createPortal } from "react-dom";
import { useQuickOpenStore } from "renderer/commandPalette/ui/QuickOpen/quickOpenStore";
import { useV2UserPreferences } from "renderer/hooks/useV2UserPreferences";
import { useHotkey } from "renderer/hotkeys";
+import { electronTrpc } from "renderer/lib/electron-trpc";
+import { RightSidebarToggle } from "renderer/routes/_authenticated/_dashboard/components/TopBar/components/RightSidebarToggle";
+import { WindowControls } from "renderer/routes/_authenticated/_dashboard/components/TopBar/components/WindowControls";
import { CommandPalette } from "renderer/screens/main/components/CommandPalette";
import { ResizablePanel } from "renderer/screens/main/components/ResizablePanel";
import { getV2NotificationSourcesForTab } from "renderer/stores/v2-notifications";
@@ -241,10 +244,6 @@ function V2WorkspaceContent() {
// The sidebar slot lives at the dashboard layout level (next to TopBar) so
// the sidebar runs full-height.
const sidebarSlotEl = useSlotElement("workspace-right-sidebar-slot");
- // TopBar slot for the run button when the presets bar (its usual home) is
- // hidden. The button renders here via portal so it keeps this page's
- // context (pane store, workspace providers) while appearing in the TopBar.
- const runButtonSlotEl = useSlotElement("workspace-topbar-run-slot");
useWorkspaceHotkeys({
store,
@@ -260,6 +259,10 @@ function V2WorkspaceContent() {
void workspaceRun.toggleWorkspaceRun();
});
+ const { data: platform } = electronTrpc.window.getPlatform.useQuery();
+ // Default to Mac while loading so window controls don't flash in.
+ const isMac = platform === undefined || platform === "darwin";
+
const workspaceRunButton = (
) : null
}
@@ -316,10 +318,15 @@ function V2WorkspaceContent() {
/>
)}
renderTabBarTrailing={() => (
-
+
+
+ {workspaceRunButton}
+
+ {!isMac && }
+
)}
renderEmptyState={() => (
- {!showPresetsBar &&
- runButtonSlotEl &&
- createPortal(workspaceRunButton, runButtonSlotEl)}
{sidebarOpen &&
sidebarSlotEl &&
createPortal(
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/headerTabStyles.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/headerTabStyles.ts
index cf06bacf244..6f0261fc115 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/headerTabStyles.ts
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/headerTabStyles.ts
@@ -3,22 +3,35 @@ import { cn } from "@superset/ui/utils";
const SIDEBAR_HEADER_TAB_ACTIVE_CLASS_NAME = "text-foreground bg-border/30";
const SIDEBAR_HEADER_TAB_INACTIVE_CLASS_NAME =
"text-muted-foreground/70 hover:text-muted-foreground hover:bg-tertiary/20";
+// Inverted scheme: the shaded block marks the inactive tabs while the active
+// tab blends into the panel below it. Every state keeps a 1px border on all
+// sides (transparent where hidden) so tabs don't shift when switching.
+const SIDEBAR_HEADER_TAB_ACTIVE_INVERTED_CLASS_NAME =
+ "text-foreground border border-border border-b-transparent";
+const SIDEBAR_HEADER_TAB_INACTIVE_INVERTED_CLASS_NAME =
+ "bg-border/30 text-muted-foreground/70 hover:text-muted-foreground hover:bg-border/20 border border-transparent border-b-border";
export function getSidebarHeaderTabButtonClassName({
isActive,
compact = false,
+ inverted = false,
}: {
isActive: boolean;
compact?: boolean;
+ inverted?: boolean;
}) {
+ const activeClassName = inverted
+ ? SIDEBAR_HEADER_TAB_ACTIVE_INVERTED_CLASS_NAME
+ : SIDEBAR_HEADER_TAB_ACTIVE_CLASS_NAME;
+ const inactiveClassName = inverted
+ ? SIDEBAR_HEADER_TAB_INACTIVE_INVERTED_CLASS_NAME
+ : SIDEBAR_HEADER_TAB_INACTIVE_CLASS_NAME;
return cn(
"h-full shrink-0 transition-all",
compact
? "flex w-10 items-center justify-center"
: "flex items-center gap-1.5 px-3 text-xs",
- isActive
- ? SIDEBAR_HEADER_TAB_ACTIVE_CLASS_NAME
- : SIDEBAR_HEADER_TAB_INACTIVE_CLASS_NAME,
+ isActive ? activeClassName : inactiveClassName,
);
}
diff --git a/apps/desktop/src/renderer/stores/folder-import-intent.ts b/apps/desktop/src/renderer/stores/folder-import-intent.ts
new file mode 100644
index 00000000000..835c3be2a27
--- /dev/null
+++ b/apps/desktop/src/renderer/stores/folder-import-intent.ts
@@ -0,0 +1,16 @@
+import { create } from "zustand";
+
+// Palette commands run outside React, but the folder-import flow is a hook
+// (dialogs, host service, react-query). This intent store bridges the two:
+// commands call request(), FolderImportMount runs the flow.
+interface FolderImportIntentState {
+ tick: number;
+ request: () => void;
+}
+
+export const useFolderImportIntent = create