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
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const workspaceProvider: CommandProvider = {
run: () =>
useRemoveFromSidebarIntent.getState().request({
workspaceId: workspace.id,
workspaceName: workspace.name,
projectId: workspace.projectId ?? "",
isMain,
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { useEffect, useRef } from "react";
import {
AlertDialog,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@superset/ui/alert-dialog";
import { Button } from "@superset/ui/button";
import { useCallback } from "react";
import { useNavigateAwayFromWorkspace } from "renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useNavigateAwayFromWorkspace";
import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState";
import { useRemoveFromSidebarIntent } from "renderer/stores/remove-workspace-from-sidebar-intent";
Expand All @@ -9,11 +18,16 @@ export function RemoveFromSidebarMount() {
const { hideWorkspaceInSidebar, removeWorkspaceFromSidebar } =
useDashboardSidebarState();
const { navigateAwayFromWorkspace } = useNavigateAwayFromWorkspace();
const lastTickRef = useRef(0);

useEffect(() => {
if (!target || target.tick === lastTickRef.current) return;
lastTickRef.current = target.tick;
const handleOpenChange = useCallback(
(open: boolean) => {
if (!open) clear();
},
[clear],
);

const handleConfirm = useCallback(() => {
if (!target) return;
navigateAwayFromWorkspace(target.workspaceId);
if (target.isMain) {
hideWorkspaceInSidebar(target.workspaceId, target.projectId);
Expand All @@ -29,5 +43,37 @@ export function RemoveFromSidebarMount() {
clear,
]);

return null;
return (
<AlertDialog open={!!target} onOpenChange={handleOpenChange}>
<AlertDialogContent className="max-w-[360px] gap-0 p-0">
<AlertDialogHeader className="px-4 pt-4 pb-2">
<AlertDialogTitle className="font-medium">
Remove "{target?.workspaceName ?? "workspace"}" from sidebar?
</AlertDialogTitle>
<AlertDialogDescription>
The workspace itself isn't deleted — you can always add it back from
the Workspaces page.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter className="px-4 pb-4 pt-2 flex-row justify-end gap-2">
<Button
variant="ghost"
size="sm"
className="h-7 px-3 text-xs"
onClick={() => handleOpenChange(false)}
>
Cancel
</Button>
<Button
variant="destructive"
size="sm"
className="h-7 px-3 text-xs"
onClick={handleConfirm}
>
Remove
</Button>
</AlertDialogFooter>
Comment on lines +59 to +75
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Plain Button bypasses AlertDialog accessibility primitives

Radix UI's AlertDialogAction and AlertDialogCancel carry built-in accessibility semantics for the alertdialog role — AlertDialogCancel receives focus by default on open, and both handle keyboard dismiss correctly with their ARIA roles. Using plain Button components means those semantics are absent. The current implementation works visually, but screen readers and keyboard-only users may not get the expected cancel-first focus order or the correct element roles.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/commandPalette/ui/RemoveFromSidebarMount/RemoveFromSidebarMount.tsx
Line: 59-75

Comment:
**Plain `Button` bypasses `AlertDialog` accessibility primitives**

Radix UI's `AlertDialogAction` and `AlertDialogCancel` carry built-in accessibility semantics for the `alertdialog` role — `AlertDialogCancel` receives focus by default on open, and both handle keyboard dismiss correctly with their ARIA roles. Using plain `Button` components means those semantics are absent. The current implementation works visually, but screen readers and keyboard-only users may not get the expected cancel-first focus order or the correct element roles.

How can I resolve this? If you propose a fix, please make it concise.

</AlertDialogContent>
</AlertDialog>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { useCopyToClipboard } from "renderer/hooks/useCopyToClipboard";
import { getHostServiceClientByUrl } from "renderer/lib/host-service-client";
import { electronTrpcClient } from "renderer/lib/trpc-client";
import { useDashboardSidebarSectionRename } from "renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSectionRenameContext";
import { useNavigateAwayFromWorkspace } from "renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useNavigateAwayFromWorkspace";
import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState";
import { useOptimisticCollectionActions } from "renderer/routes/_authenticated/hooks/useOptimisticCollectionActions";
import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider";
import { useRemoveFromSidebarIntent } from "renderer/stores/remove-workspace-from-sidebar-intent";
import {
useV2NotificationStore,
useV2WorkspaceIsUnread,
Expand All @@ -31,7 +31,6 @@ export function useDashboardSidebarWorkspaceItemActions({
}: UseDashboardSidebarWorkspaceItemActionsOptions) {
const navigate = useNavigate();
const matchRoute = useMatchRoute();
const { navigateAwayFromWorkspace } = useNavigateAwayFromWorkspace();
const { activeHostUrl } = useLocalHostService();
const { copyToClipboard } = useCopyToClipboard();
const { v2Workspaces: workspaceActions } = useOptimisticCollectionActions();
Expand All @@ -41,12 +40,8 @@ export function useDashboardSidebarWorkspaceItemActions({
);
const setManualUnread = useV2NotificationStore((s) => s.setManualUnread);
const isUnread = useV2WorkspaceIsUnread(workspaceId);
const {
createSection,
hideWorkspaceInSidebar,
moveWorkspaceToSection,
removeWorkspaceFromSidebar,
} = useDashboardSidebarState();
const { createSection, moveWorkspaceToSection, removeWorkspaceFromSidebar } =
useDashboardSidebarState();

const [isRenaming, setIsRenaming] = useState(false);
const [renameValue, setRenameValue] = useState(workspaceName);
Expand Down Expand Up @@ -89,12 +84,12 @@ export function useDashboardSidebarWorkspaceItemActions({
};

const handleRemoveFromSidebar = () => {
navigateAwayFromWorkspace(workspaceId);
if (isMainWorkspace) {
hideWorkspaceInSidebar(workspaceId, projectId);
return;
}
removeWorkspaceFromSidebar(workspaceId);
useRemoveFromSidebarIntent.getState().request({
workspaceId,
workspaceName,
projectId,
isMain: isMainWorkspace,
});
};

const handleCreateSection = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type {
import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState";
import { PRIcon } from "renderer/screens/main/components/PRIcon/PRIcon";
import { getRelativeTime } from "renderer/screens/main/components/WorkspacesListView/utils";
import { useRemoveFromSidebarIntent } from "renderer/stores/remove-workspace-from-sidebar-intent";
import { V2_WORKSPACES_ROW_GRID } from "../../constants";

interface V2WorkspaceRowProps {
Expand All @@ -47,11 +48,7 @@ export function V2WorkspaceRow({
}: V2WorkspaceRowProps) {
const navigate = useNavigate();
const { gateFeature } = usePaywall();
const {
ensureWorkspaceInSidebar,
hideWorkspaceInSidebar,
removeWorkspaceFromSidebar,
} = useDashboardSidebarState();
const { ensureWorkspaceInSidebar } = useDashboardSidebarState();
const isMainWorkspace = workspace.type === "main";

const HostIcon = hostIconFor(workspace.hostType);
Expand Down Expand Up @@ -95,18 +92,18 @@ export function V2WorkspaceRow({
event.preventDefault();
return;
}
if (isMainWorkspace) {
hideWorkspaceInSidebar(workspace.id, workspace.projectId);
return;
}
removeWorkspaceFromSidebar(workspace.id);
useRemoveFromSidebarIntent.getState().request({
workspaceId: workspace.id,
workspaceName: workspace.name,
projectId: workspace.projectId,
isMain: isMainWorkspace,
});
},
[
hideWorkspaceInSidebar,
isCurrentRoute,
isMainWorkspace,
removeWorkspaceFromSidebar,
workspace.id,
workspace.name,
workspace.projectId,
],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { create } from "zustand";

export interface RemoveFromSidebarTarget {
workspaceId: string;
workspaceName: string;
projectId: string;
isMain: boolean;
tick: number;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 tick is now dead state

tick is incremented on every request() call but is no longer consumed anywhere — the old useEffect guard (target.tick === lastTickRef.current) was removed when RemoveFromSidebarMount became an AlertDialog. The dialog open/close state is driven entirely by !!target, so the field serves no runtime purpose. Consider removing tick from RemoveFromSidebarTarget and the request implementation to avoid confusion for future readers.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/stores/remove-workspace-from-sidebar-intent.ts
Line: 8

Comment:
**`tick` is now dead state**

`tick` is incremented on every `request()` call but is no longer consumed anywhere — the old `useEffect` guard (`target.tick === lastTickRef.current`) was removed when `RemoveFromSidebarMount` became an `AlertDialog`. The dialog open/close state is driven entirely by `!!target`, so the field serves no runtime purpose. Consider removing `tick` from `RemoveFromSidebarTarget` and the `request` implementation to avoid confusion for future readers.

How can I resolve this? If you propose a fix, please make it concise.

Expand Down
Loading