- {/* Group Tab Header */}
-
-
- {/* Nested Tabs - Make the entire area droppable */}
- {isExpanded && tab.tabs && (
-
-
- {tab.tabs.map((childTab) =>
- renderTab(childTab, tab.id, level + 1),
- )}
-
-
- )}
+
+
+
+
+
+
+ handleRenameGroup(tab.id, tab.name)}
+ >
+
+ Rename
+
+ handleUngroupTab(tab.id)}>
+
+ Ungroup Tabs
+
+
+
);
}
- // Regular tab (terminal, editor, etc.)
+ // Regular tab - attach drag handle for dragging
return (
-
-
+ {
+ handleTabSelect(wtId, tabId, shiftKey);
+ }}
onTabRemove={handleTabRemove}
onGroupTabs={handleGroupTabs}
onMoveOutOfGroup={handleMoveOutOfGroup}
@@ -1162,13 +1057,51 @@ export function WorktreeItem({
{/* Tabs List */}
- {/* Render tabs with collapsible groups */}
- {
+ if (nodes.length > 0) {
+ const node = nodes[0];
+ handleTabSelect(worktree.id, node.id, false);
+ }
+ }}
+ onToggle={(id) => {
+ const node = treeData.find((item) => item.id === id);
+ if (node && node.tab.type === "group") {
+ setExpandedGroupTabs((prev) => {
+ const next = new Set(prev);
+ if (next.has(id)) {
+ next.delete(id);
+ } else {
+ next.add(id);
+ }
+ return next;
+ });
+ }
+ }}
+ openByDefault={true}
+ initialOpenState={Object.fromEntries(
+ treeData
+ .filter((item) => item.tab.type === "group")
+ .map((item) => [item.id, true]),
+ )}
+ rowHeight={TREE_ROW_HEIGHT}
+ indent={12}
+ disableDrop={(args) => {
+ // Prevent dropping group tabs into other groups
+ const draggedTab = args.dragNodes[0]?.data.tab as Tab;
+ const targetParentTab = args.parentNode?.data.tab as Tab;
+ return (
+ draggedTab?.type === "group" &&
+ targetParentTab?.type === "group"
+ );
+ }}
>
- {tabs.map((tab) => renderTab(tab, undefined, 0))}
-
+ {renderNode}
+
{/* Remove Worktree Confirmation Dialog */}
diff --git a/apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItemArborist.tsx b/apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItemArborist.tsx
new file mode 100644
index 00000000000..6f359f5d066
--- /dev/null
+++ b/apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItemArborist.tsx
@@ -0,0 +1,450 @@
+import { Tree } from "react-arborist";
+import type { NodeApi } from "react-arborist";
+import type { TreeApi } from "react-arborist";
+import { Button } from "@superset/ui/button";
+import {
+ ContextMenu,
+ ContextMenuContent,
+ ContextMenuItem,
+ ContextMenuTrigger,
+} from "@superset/ui/context-menu";
+import {
+ ChevronRight,
+ Edit2,
+ FolderOpen,
+} from "lucide-react";
+import { useEffect, useId, useRef, useState } from "react";
+import type { Tab, Worktree } from "shared/types";
+import { WorktreePortsList } from "../WorktreePortsList";
+import { GitStatusDialog } from "./components/GitStatusDialog";
+import { TabItem } from "./components/TabItem";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "renderer/components/ui/dialog";
+
+interface WorktreeItemProps {
+ worktree: Worktree;
+ workspaceId: string;
+ activeWorktreeId: string | null;
+ onTabSelect: (worktreeId: string, tabId: string) => void;
+ onReload: () => void;
+ onUpdateWorktree: (updatedWorktree: Worktree) => void;
+ selectedTabId: string | undefined;
+ hasPortForwarding?: boolean;
+ onCloneWorktree: () => void;
+}
+
+// Convert Tab[] to react-arborist format
+function convertTabsToTreeData(tabs: Tab[]): Array<{ id: string; name: string; tab: Tab; children?: Array<{ id: string; name: string; tab: Tab }> }> {
+ return tabs.map((tab) => {
+ const node: { id: string; name: string; tab: Tab; children?: Array<{ id: string; name: string; tab: Tab }> } = {
+ id: tab.id,
+ name: tab.name,
+ tab,
+ };
+ if (tab.type === "group" && tab.tabs) {
+ node.children = convertTabsToTreeData(tab.tabs);
+ }
+ return node;
+ });
+}
+
+// Convert react-arborist data back to Tab[]
+function convertTreeDataToTabs(nodes: NodeApi[]): Tab[] {
+ return nodes.map((node) => {
+ const tab = node.data.tab as Tab;
+ if (tab.type === "group" && node.children && node.children.length > 0) {
+ return {
+ ...tab,
+ tabs: convertTreeDataToTabs(node.children),
+ };
+ }
+ return tab;
+ });
+}
+
+export function WorktreeItem({
+ worktree,
+ workspaceId,
+ activeWorktreeId,
+ onTabSelect,
+ onReload,
+ onUpdateWorktree,
+ selectedTabId,
+ hasPortForwarding = false,
+ onCloneWorktree: _onCloneWorktree,
+}: WorktreeItemProps) {
+ const [expandedGroupTabs, setExpandedGroupTabs] = useState>(
+ new Set(),
+ );
+ const [selectedTabIds, setSelectedTabIds] = useState>(new Set());
+ const [lastClickedTabId, setLastClickedTabId] = useState(null);
+
+ // Dialog states
+ const [showRemoveDialog, setShowRemoveDialog] = useState(false);
+ const [showMergeDialog, setShowMergeDialog] = useState(false);
+ const [showErrorDialog, setShowErrorDialog] = useState(false);
+ const [showGitStatusDialog, setShowGitStatusDialog] = useState(false);
+ const [errorMessage, setErrorMessage] = useState("");
+ const [errorTitle, setErrorTitle] = useState("");
+ const [mergeWarning, setMergeWarning] = useState("");
+ const [removeWarning, setRemoveWarning] = useState("");
+
+ const isActive = activeWorktreeId === worktree.id;
+ const tabs = Array.isArray(worktree.tabs) ? worktree.tabs : [];
+ const treeData = convertTabsToTreeData(tabs);
+
+ // Auto-expand group tabs that contain the selected tab
+ useEffect(() => {
+ if (!selectedTabId) return;
+ const findParentGroup = (tabs: Tab[], tabId: string): Tab | null => {
+ for (const tab of tabs) {
+ if (tab.type === "group" && tab.tabs) {
+ if (tab.tabs.some((t) => t.id === tabId)) return tab;
+ const found = findParentGroup(tab.tabs, tabId);
+ if (found) return found;
+ }
+ }
+ return null;
+ };
+ const parentGroup = findParentGroup(tabs, selectedTabId);
+ if (parentGroup) {
+ setExpandedGroupTabs((prev) => new Set(prev).add(parentGroup.id));
+ }
+ }, [selectedTabId, tabs]);
+
+ // Handle tab selection
+ const handleTabSelect = (
+ worktreeId: string,
+ tabId: string,
+ shiftKey: boolean,
+ ) => {
+ if (shiftKey && lastClickedTabId) {
+ // Shift-click: select range
+ const allTabs = tabs.flatMap((t) =>
+ t.type === "group" && t.tabs ? t.tabs : [t],
+ );
+ const lastIndex = allTabs.findIndex((t) => t.id === lastClickedTabId);
+ const currentIndex = allTabs.findIndex((t) => t.id === tabId);
+ if (lastIndex !== -1 && currentIndex !== -1) {
+ const start = Math.min(lastIndex, currentIndex);
+ const end = Math.max(lastIndex, currentIndex);
+ const rangeTabIds = allTabs.slice(start, end + 1).map((t) => t.id);
+ setSelectedTabIds(new Set(rangeTabIds));
+ }
+ } else {
+ setSelectedTabIds(new Set([tabId]));
+ setLastClickedTabId(tabId);
+ }
+ onTabSelect(worktreeId, tabId);
+ };
+
+ // Handle drag and drop (move)
+ const handleMove = async (args: {
+ dragIds: string[];
+ dragNodes: NodeApi<{ id: string; name: string; tab: Tab; children?: Array<{ id: string; name: string; tab: Tab }> }>[];
+ parentId: string | null;
+ parentNode: NodeApi<{ id: string; name: string; tab: Tab; children?: Array<{ id: string; name: string; tab: Tab }> }> | null;
+ index: number;
+ }) => {
+ if (args.dragNodes.length === 0) return;
+
+ const draggedNode = args.dragNodes[0];
+ const draggedTab = draggedNode.data.tab as Tab;
+
+ if (!draggedTab || draggedTab.type === "group") return;
+
+ const draggedTabId = draggedTab.id;
+ const sourceParent = draggedNode.parent;
+ const sourceParentTabId = sourceParent?.data.tab?.type === "group" ? sourceParent.id : null;
+ const targetParentTabId = args.parentNode?.data.tab?.type === "group" ? args.parentNode.id : null;
+
+ // Don't move if already in the same position
+ if (sourceParentTabId === targetParentTabId) {
+ return;
+ }
+
+ try {
+ const result = await window.ipcRenderer.invoke("tab-move", {
+ workspaceId,
+ worktreeId: worktree.id,
+ tabId: draggedTabId,
+ sourceParentTabId: sourceParentTabId || undefined,
+ targetParentTabId: targetParentTabId || undefined,
+ targetIndex: args.index,
+ });
+
+ if (result.success) {
+ onReload();
+ onTabSelect(worktree.id, draggedTabId);
+ } else {
+ console.error("Failed to move tab:", result.error);
+ }
+ } catch (error) {
+ console.error("Error moving tab:", error);
+ }
+ };
+
+ // Handle tab removal
+ const handleTabRemove = async (tabId: string) => {
+ try {
+ const result = await window.ipcRenderer.invoke("tab-delete", {
+ workspaceId,
+ worktreeId: worktree.id,
+ tabId,
+ });
+
+ if (result.success) {
+ onReload();
+ } else {
+ console.error("Failed to delete tab:", result.error);
+ }
+ } catch (error) {
+ console.error("Error deleting tab:", error);
+ }
+ };
+
+ // Handle tab rename
+ const handleTabRename = async (tabId: string, newName: string) => {
+ try {
+ const result = await window.ipcRenderer.invoke("tab-update-name", {
+ workspaceId,
+ worktreeId: worktree.id,
+ tabId,
+ name: newName,
+ });
+
+ if (result.success) {
+ onReload();
+ } else {
+ alert(`Failed to rename tab: ${result.error}`);
+ }
+ } catch (error) {
+ console.error("Error renaming tab:", error);
+ alert("Failed to rename tab");
+ }
+ };
+
+ // Handle group rename
+ const handleRenameGroup = async (groupTabId: string, newName: string) => {
+ await handleTabRename(groupTabId, newName);
+ };
+
+ // Handle ungroup
+ const handleUngroupTab = async (groupTabId: string) => {
+ const groupTab = tabs.find((t) => t.id === groupTabId);
+ if (!groupTab || groupTab.type !== "group" || !groupTab.tabs) return;
+
+ for (const childTab of groupTab.tabs) {
+ await window.ipcRenderer.invoke("tab-move", {
+ workspaceId,
+ worktreeId: worktree.id,
+ tabId: childTab.id,
+ sourceParentTabId: groupTabId,
+ targetParentTabId: undefined,
+ targetIndex: tabs.length,
+ });
+ }
+
+ await window.ipcRenderer.invoke("tab-delete", {
+ workspaceId,
+ worktreeId: worktree.id,
+ tabId: groupTabId,
+ });
+
+ onReload();
+ };
+
+ // Handle grouping selected tabs
+ const handleGroupTabs = async (tabIds: string[]) => {
+ try {
+ const result = await window.ipcRenderer.invoke("tab-create", {
+ workspaceId,
+ worktreeId: worktree.id,
+ name: `Tab Group`,
+ type: "group",
+ });
+
+ if (!result.success || !result.tab) {
+ console.error("Failed to create group tab:", result.error);
+ return;
+ }
+
+ const groupTabId = result.tab.id;
+
+ for (const tabId of tabIds) {
+ await window.ipcRenderer.invoke("tab-move", {
+ workspaceId,
+ worktreeId: worktree.id,
+ tabId,
+ targetParentTabId: groupTabId,
+ targetIndex: 0,
+ });
+ }
+
+ onReload();
+ setExpandedGroupTabs((prev) => new Set(prev).add(groupTabId));
+ onTabSelect(worktree.id, groupTabId);
+ setSelectedTabIds(new Set());
+ setLastClickedTabId(null);
+ } catch (error) {
+ console.error("Error grouping tabs:", error);
+ }
+ };
+
+ // Handle moving tab out of group
+ const handleMoveOutOfGroup = async (tabId: string, parentTabId: string) => {
+ try {
+ const result = await window.ipcRenderer.invoke("tab-move", {
+ workspaceId,
+ worktreeId: worktree.id,
+ tabId,
+ sourceParentTabId: parentTabId,
+ targetParentTabId: undefined,
+ targetIndex: tabs.length,
+ });
+
+ if (result.success) {
+ onReload();
+ onTabSelect(worktree.id, tabId);
+ } else {
+ console.error("Failed to move tab out of group:", result.error);
+ }
+ } catch (error) {
+ console.error("Error moving tab out of group:", error);
+ }
+ };
+
+ if (!isActive) {
+ return null;
+ }
+
+ // Render node content
+ const renderNode = (props: {
+ node: NodeApi<{ id: string; name: string; tab: Tab; children?: Array<{ id: string; name: string; tab: Tab }> }>;
+ style: React.CSSProperties;
+ tree: TreeApi<{ id: string; name: string; tab: Tab; children?: Array<{ id: string; name: string; tab: Tab }> }>;
+ dragHandle?: (el: HTMLDivElement | null) => void;
+ preview?: boolean;
+ }) => {
+ const { node, style } = props;
+ const tab = node.data.tab as Tab;
+ const isGroup = tab.type === "group";
+ const isSelected = selectedTabId === tab.id;
+ const isExpanded = node.isOpen;
+
+ if (isGroup) {
+ return (
+
+
+
+
+
+
+ handleRenameGroup(tab.id, tab.name)}>
+
+ Rename
+
+ handleUngroupTab(tab.id)}>
+
+ Ungroup Tabs
+
+
+
+
+ );
+ }
+
+ return (
+
+ {
+ handleTabSelect(wtId, tabId, shiftKey);
+ }}
+ onTabRemove={handleTabRemove}
+ onGroupTabs={handleGroupTabs}
+ onMoveOutOfGroup={handleMoveOutOfGroup}
+ onTabRename={handleTabRename}
+ />
+
+ );
+ };
+
+ return (
+
+ {hasPortForwarding && (
+
+ )}
+
+
+ {
+ if (nodes.length > 0) {
+ const node = nodes[0];
+ handleTabSelect(worktree.id, node.id, false);
+ }
+ }}
+ onToggle={(id) => {
+ const node = treeData.find((item) => item.id === id);
+ if (node && node.tab.type === "group") {
+ setExpandedGroupTabs((prev) => {
+ const next = new Set(prev);
+ if (next.has(id)) {
+ next.delete(id);
+ } else {
+ next.add(id);
+ }
+ return next;
+ });
+ }
+ }}
+ openByDefault={false}
+ initialOpenState={Object.fromEntries(
+ treeData
+ .filter((item) => item.tab.type === "group" && expandedGroupTabs.has(item.id))
+ .map((item) => [item.id, true])
+ )}
+ >
+ {renderNode}
+
+
+
+ {/* Dialogs remain the same - keeping them for now */}
+
+ );
+}
+
diff --git a/apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/components/TabItem/TabItem.tsx b/apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/components/TabItem/TabItem.tsx
index 2c983d4f94e..72ff0e0209f 100644
--- a/apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/components/TabItem/TabItem.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/components/TabItem/TabItem.tsx
@@ -63,7 +63,14 @@ export function TabItem({
onTabRemove?.(tab.id);
};
+ const handleMouseDown = (e: React.MouseEvent) => {
+ // Stop propagation to prevent drag from starting when clicking the button
+ e.stopPropagation();
+ };
+
const handleClick = (e: React.MouseEvent) => {
+ // Stop propagation to prevent drag from starting
+ e.stopPropagation();
if (!isEditing) {
onTabSelect(worktreeId, tab.id, e.shiftKey);
}
@@ -144,6 +151,7 @@ export function TabItem({
? "bg-blue-900/30 text-blue-200"
: "hover:bg-neutral-800/40 text-neutral-400 hover:text-neutral-300"
}`}
+ onMouseDown={handleMouseDown}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
>
diff --git a/bun.lock b/bun.lock
index 0078f7c349d..49e7a46b588 100644
--- a/bun.lock
+++ b/bun.lock
@@ -63,6 +63,9 @@
"lucide-react": "^0.553.0",
"node-pty": "1.1.0-beta30",
"react": "^19.1.1",
+ "react-arborist": "^3.4.3",
+ "react-dnd": "^16.0.1",
+ "react-dnd-html5-backend": "^16.0.1",
"react-dom": "^19.1.1",
"react-mosaic-component": "^6.1.1",
"react-resizable-panels": "^3.0.6",
@@ -2107,6 +2110,8 @@
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
+ "memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="],
+
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
@@ -2423,6 +2428,8 @@
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
+ "react-arborist": ["react-arborist@3.4.3", "", { "dependencies": { "react-dnd": "^14.0.3", "react-dnd-html5-backend": "^14.0.3", "react-window": "^1.8.11", "redux": "^5.0.0", "use-sync-external-store": "^1.2.0" }, "peerDependencies": { "react": ">= 16.14", "react-dom": ">= 16.14" } }, "sha512-yFnq1nIQhT2uJY4TZVz2tgAiBb9lxSyvF4vC3S8POCK8xLzjGIxVv3/4dmYquQJ7AHxaZZArRGHiHKsEewKdTQ=="],
+
"react-compiler-runtime": ["react-compiler-runtime@19.1.0-rc.1-rc-af1b7da-20250421", "", { "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental" } }, "sha512-Til/juI+Zfq+eYpGYn9lFxqW5RyJDs3ThOxmg0757aMrPpfx/Zb0SnGMVJhF3vw+bEQjJiD+xPFD3+kE0WbyeA=="],
"react-dnd": ["react-dnd@16.0.1", "", { "dependencies": { "@react-dnd/invariant": "^4.0.1", "@react-dnd/shallowequal": "^4.0.1", "dnd-core": "^16.0.1", "fast-deep-equal": "^3.1.3", "hoist-non-react-statics": "^3.3.2" }, "peerDependencies": { "@types/hoist-non-react-statics": ">= 3.3.1", "@types/node": ">= 12", "@types/react": ">= 16", "react": ">= 16.14" }, "optionalPeers": ["@types/hoist-non-react-statics", "@types/node", "@types/react"] }, "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q=="],
@@ -2463,6 +2470,8 @@
"react-use-measure": ["react-use-measure@2.1.7", "", { "peerDependencies": { "react": ">=16.13", "react-dom": ">=16.13" }, "optionalPeers": ["react-dom"] }, "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg=="],
+ "react-window": ["react-window@1.8.11", "", { "dependencies": { "@babel/runtime": "^7.0.0", "memoize-one": ">=3.1.1 <6" }, "peerDependencies": { "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ=="],
+
"read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="],
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
@@ -2477,7 +2486,7 @@
"recma-stringify": ["recma-stringify@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-to-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g=="],
- "redux": ["redux@4.2.1", "", { "dependencies": { "@babel/runtime": "^7.9.2" } }, "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w=="],
+ "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="],
"refractor": ["refractor@5.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/prismjs": "^1.0.0", "hastscript": "^9.0.0", "parse-entities": "^4.0.0" } }, "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw=="],
@@ -3071,6 +3080,8 @@
"dir-compare/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+ "dnd-core/redux": ["redux@4.2.1", "", { "dependencies": { "@babel/runtime": "^7.9.2" } }, "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w=="],
+
"dotenv-expand/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
"electron/@types/node": ["@types/node@22.19.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA=="],
@@ -3207,6 +3218,10 @@
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
+ "react-arborist/react-dnd": ["react-dnd@14.0.5", "", { "dependencies": { "@react-dnd/invariant": "^2.0.0", "@react-dnd/shallowequal": "^2.0.0", "dnd-core": "14.0.1", "fast-deep-equal": "^3.1.3", "hoist-non-react-statics": "^3.3.2" }, "peerDependencies": { "@types/hoist-non-react-statics": ">= 3.3.1", "@types/node": ">= 12", "@types/react": ">= 16", "react": ">= 16.14" }, "optionalPeers": ["@types/hoist-non-react-statics", "@types/node", "@types/react"] }, "sha512-9i1jSgbyVw0ELlEVt/NkCUkxy1hmhJOkePoCH713u75vzHGyXhPDm28oLfc2NMSBjZRM1Y+wRjHXJT3sPrTy+A=="],
+
+ "react-arborist/react-dnd-html5-backend": ["react-dnd-html5-backend@14.1.0", "", { "dependencies": { "dnd-core": "14.0.1" } }, "sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw=="],
+
"react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"remark-reading-time/estree-util-is-identifier-name": ["estree-util-is-identifier-name@2.1.0", "", {}, "sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ=="],
@@ -3477,6 +3492,14 @@
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
+ "react-arborist/react-dnd/@react-dnd/invariant": ["@react-dnd/invariant@2.0.0", "", {}, "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw=="],
+
+ "react-arborist/react-dnd/@react-dnd/shallowequal": ["@react-dnd/shallowequal@2.0.0", "", {}, "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg=="],
+
+ "react-arborist/react-dnd/dnd-core": ["dnd-core@14.0.1", "", { "dependencies": { "@react-dnd/asap": "^4.0.0", "@react-dnd/invariant": "^2.0.0", "redux": "^4.1.1" } }, "sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A=="],
+
+ "react-arborist/react-dnd-html5-backend/dnd-core": ["dnd-core@14.0.1", "", { "dependencies": { "@react-dnd/asap": "^4.0.0", "@react-dnd/invariant": "^2.0.0", "redux": "^4.1.1" } }, "sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A=="],
+
"remark-reading-time/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
"remark-reading-time/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="],
@@ -3531,6 +3554,16 @@
"jest-runtime/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+ "react-arborist/react-dnd-html5-backend/dnd-core/@react-dnd/asap": ["@react-dnd/asap@4.0.1", "", {}, "sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg=="],
+
+ "react-arborist/react-dnd-html5-backend/dnd-core/@react-dnd/invariant": ["@react-dnd/invariant@2.0.0", "", {}, "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw=="],
+
+ "react-arborist/react-dnd-html5-backend/dnd-core/redux": ["redux@4.2.1", "", { "dependencies": { "@babel/runtime": "^7.9.2" } }, "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w=="],
+
+ "react-arborist/react-dnd/dnd-core/@react-dnd/asap": ["@react-dnd/asap@4.0.1", "", {}, "sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg=="],
+
+ "react-arborist/react-dnd/dnd-core/redux": ["redux@4.2.1", "", { "dependencies": { "@babel/runtime": "^7.9.2" } }, "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w=="],
+
"temp/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],