diff --git a/apps/x/apps/renderer/package.json b/apps/x/apps/renderer/package.json index c37b6066..397dae9c 100644 --- a/apps/x/apps/renderer/package.json +++ b/apps/x/apps/renderer/package.json @@ -11,10 +11,12 @@ "dependencies": { "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-hover-card": "^1.1.15", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 5955160e..a26de040 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -222,7 +222,6 @@ const chatHistory = [ function App() { // File browser state (for Knowledge section) - const [_knowledgeContent, setKnowledgeContent] = useState('') const [selectedPath, setSelectedPath] = useState(null) const [fileContent, setFileContent] = useState('') const [tree, setTree] = useState([]) @@ -238,65 +237,40 @@ function App() { const [isProcessing, setIsProcessing] = useState(false) const [agentId] = useState('copilot') - // Load directory and merge into tree - const loadDirectory = useCallback(async (path: string = '') => { + // Load directory tree + const loadDirectory = useCallback(async () => { try { const result = await window.ipc.invoke('workspace:readdir', { - path, + path: 'knowledge', opts: { recursive: true, includeHidden: false } }) - const tree = buildTree(result) - return tree + return buildTree(result) } catch (err) { console.error('Failed to load directory:', err) return [] } }, []) - // Load knowledge file content - const loadKnowledge = useCallback(async () => { - try { - const result = await window.ipc.invoke('workspace:readFile', { - path: 'knowledge', - encoding: 'utf8' - }) - return result.data - } catch (err) { - console.error('Failed to load knowledge file:', err) - return '' - } - }, []) - - // Load initial tree and knowledge content + // Load initial tree useEffect(() => { - async function process() { - const [treeData, content] = await Promise.all([ - loadDirectory(), - loadKnowledge() - ]); - setTree(treeData) - setKnowledgeContent(content) - } - process(); - }, [loadDirectory, loadKnowledge]) + loadDirectory().then(setTree) + }, [loadDirectory]) // Listen to workspace change events useEffect(() => { const cleanup = window.ipc.on('workspace:didChange', () => { - // Reload tree and knowledge on any change - loadDirectory().then(result => setTree(result)) - loadKnowledge().then(result => setKnowledgeContent(result)) + loadDirectory().then(setTree) }) return cleanup - }, [loadDirectory, loadKnowledge]) + }, [loadDirectory]) // Load file content when selected useEffect(() => { - async function process() { - if (!selectedPath) { - setFileContent('') - return - } + if (!selectedPath) { + setFileContent('') + return + } + (async () => { try { const stat = await window.ipc.invoke('workspace:stat', { path: selectedPath }) if (stat.kind === 'file') { @@ -307,9 +281,9 @@ function App() { } } catch (err) { console.error('Failed to load file:', err) + setFileContent('') } - } - process(); + })() }, [selectedPath]) // Listen to run events @@ -520,6 +494,76 @@ function App() { setExpandedPaths(newExpanded) } + // Knowledge quick actions + const collectDirPaths = (nodes: TreeNode[]): string[] => + nodes.flatMap(n => n.kind === 'dir' ? [n.path, ...(n.children ? collectDirPaths(n.children) : [])] : []) + + // Get workspace root for full paths + const [workspaceRoot, setWorkspaceRoot] = useState('') + useEffect(() => { + window.ipc.invoke('workspace:getRoot', null).then(result => { + setWorkspaceRoot(result.root) + }) + }, []) + + const knowledgeActions = React.useMemo(() => ({ + createNote: async (parentPath: string = 'knowledge') => { + try { + const name = `untitled-${Date.now()}.md` + const fullPath = `${parentPath}/${name}` + await window.ipc.invoke('workspace:writeFile', { + path: fullPath, + data: `# New Note\n\n`, + opts: { encoding: 'utf8' } + }) + setSelectedPath(fullPath) + } catch (err) { + console.error('Failed to create note:', err) + throw err + } + }, + createFolder: async (parentPath: string = 'knowledge') => { + try { + await window.ipc.invoke('workspace:mkdir', { + path: `${parentPath}/new-folder-${Date.now()}`, + recursive: true + }) + } catch (err) { + console.error('Failed to create folder:', err) + throw err + } + }, + expandAll: () => setExpandedPaths(new Set(collectDirPaths(tree))), + collapseAll: () => setExpandedPaths(new Set()), + rename: async (oldPath: string, newName: string, isDir: boolean) => { + try { + const parts = oldPath.split('/') + // For files, ensure .md extension + const finalName = isDir ? newName : (newName.endsWith('.md') ? newName : `${newName}.md`) + parts[parts.length - 1] = finalName + const newPath = parts.join('/') + await window.ipc.invoke('workspace:rename', { from: oldPath, to: newPath }) + if (selectedPath === oldPath) setSelectedPath(newPath) + } catch (err) { + console.error('Failed to rename:', err) + throw err + } + }, + remove: async (path: string) => { + try { + await window.ipc.invoke('workspace:remove', { path, opts: { trash: true } }) + if (selectedPath === path) setSelectedPath(null) + } catch (err) { + console.error('Failed to remove:', err) + throw err + } + }, + copyPath: (path: string) => { + const fullPath = workspaceRoot ? `${workspaceRoot}/${path}` : path + navigator.clipboard.writeText(fullPath) + }, + }), [tree, selectedPath, workspaceRoot, collectDirPaths]) + const renderConversationItem = (item: ConversationItem) => { if (isChatMessage(item)) { return ( @@ -629,6 +673,7 @@ function App() { selectedPath={selectedPath} expandedPaths={expandedPaths} onSelectFile={toggleExpand} + knowledgeActions={knowledgeActions} chats={chatHistory} /> diff --git a/apps/x/apps/renderer/src/components/sidebar-content.tsx b/apps/x/apps/renderer/src/components/sidebar-content.tsx index d05796a8..0b178803 100644 --- a/apps/x/apps/renderer/src/components/sidebar-content.tsx +++ b/apps/x/apps/renderer/src/components/sidebar-content.tsx @@ -8,6 +8,7 @@ import { ChevronRight, ChevronsDownUp, ChevronsUpDown, + Copy, Database, File, FilePlus, @@ -19,8 +20,10 @@ import { MessageSquarePlus, Microscope, Network, + Pencil, Plug, Plus, + Trash2, } from "lucide-react" import { @@ -49,6 +52,14 @@ import { import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Switch } from "@/components/ui/switch" +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuTrigger, +} from "@/components/ui/context-menu" +import { Input } from "@/components/ui/input" import { useSidebarSection } from "@/contexts/sidebar-context" import { useOAuth, useAvailableProviders } from "@/hooks/useOAuth" import { toast } from "@/lib/toast" @@ -61,11 +72,22 @@ interface TreeNode { loaded?: boolean } +type KnowledgeActions = { + createNote: (parentPath?: string) => void + createFolder: (parentPath?: string) => void + expandAll: () => void + collapseAll: () => void + rename: (path: string, newName: string, isDir: boolean) => Promise + remove: (path: string) => Promise + copyPath: (path: string) => void +} + type SidebarContentPanelProps = { tree: TreeNode[] selectedPath: string | null expandedPaths: Set onSelectFile: (path: string, kind: "file" | "dir") => void + knowledgeActions: KnowledgeActions chats: { id: string; title: string; preview: string; time: string }[] } & React.ComponentProps @@ -75,12 +97,6 @@ const sectionTitles = { agents: "Agents", } -const quickActions = [ - { icon: FilePlus, label: "New Note", action: () => console.log("New note") }, - { icon: FolderPlus, label: "New Folder", action: () => console.log("New folder") }, - { icon: Network, label: "Graph View", action: () => console.log("Graph view") }, - { icon: ArrowDownAZ, label: "Sort", action: () => console.log("Sort") }, -] const agentPresets = [ { @@ -149,15 +165,11 @@ export function SidebarContentPanel({ selectedPath, expandedPaths, onSelectFile, + knowledgeActions, chats, ...props }: SidebarContentPanelProps) { const { activeSection } = useSidebarSection() - const [allExpanded, setAllExpanded] = React.useState(false) - - const toggleExpandAll = () => { - setAllExpanded(!allExpanded) - } return ( @@ -176,8 +188,7 @@ export function SidebarContentPanel({ selectedPath={selectedPath} expandedPaths={expandedPaths} onSelectFile={onSelectFile} - allExpanded={allExpanded} - onToggleExpandAll={toggleExpandAll} + actions={knowledgeActions} /> )} {activeSection === "agents" && ( @@ -232,64 +243,86 @@ function KnowledgeSection({ selectedPath, expandedPaths, onSelectFile, - allExpanded, - onToggleExpandAll, + actions, }: { tree: TreeNode[] selectedPath: string | null expandedPaths: Set onSelectFile: (path: string, kind: "file" | "dir") => void - allExpanded: boolean - onToggleExpandAll: () => void + actions: KnowledgeActions }) { + const isExpanded = expandedPaths.size > 0 + + const quickActions = [ + { icon: FilePlus, label: "New Note", action: () => actions.createNote() }, + { icon: FolderPlus, label: "New Folder", action: () => actions.createFolder() }, + { icon: Network, label: "Graph View", action: () => {} }, + { icon: ArrowDownAZ, label: "Sort", action: () => {} }, + ] + return ( - -
- {quickActions.map((action) => ( - - - - - {action.label} - - ))} - - - - - - {allExpanded ? "Collapse All" : "Expand All"} - - -
- - - {tree.map((item, index) => ( - - ))} - - -
+ + + +
+ {quickActions.map((action) => ( + + + + + {action.label} + + ))} + + + + + + {isExpanded ? "Collapse All" : "Expand All"} + + +
+ + + {tree.map((item, index) => ( + + ))} + + +
+
+ + actions.createNote()}> + + New Note + + actions.createFolder()}> + + New Folder + + +
) } @@ -299,59 +332,193 @@ function Tree({ selectedPath, expandedPaths, onSelect, + actions, }: { item: TreeNode selectedPath: string | null expandedPaths: Set onSelect: (path: string, kind: "file" | "dir") => void + actions: KnowledgeActions }) { - const hasChildren = item.children && item.children.length > 0 + const isDir = item.kind === 'dir' const isExpanded = expandedPaths.has(item.path) const isSelected = selectedPath === item.path + const [isRenaming, setIsRenaming] = useState(false) + const isSubmittingRef = React.useRef(false) - if (!hasChildren) { + // For files, strip .md extension for editing + const baseName = !isDir && item.name.endsWith('.md') + ? item.name.slice(0, -3) + : item.name + const [newName, setNewName] = useState(baseName) + + // Sync newName when baseName changes (e.g., after external rename) + React.useEffect(() => { + setNewName(baseName) + }, [baseName]) + + const handleRename = async () => { + // Prevent double submission + if (isSubmittingRef.current) return + isSubmittingRef.current = true + + const trimmedName = newName.trim() + if (trimmedName && trimmedName !== baseName) { + try { + await actions.rename(item.path, trimmedName, isDir) + toast('Renamed successfully', 'success') + } catch (err) { + toast('Failed to rename', 'error') + } + } + setIsRenaming(false) + // Reset after a small delay to prevent blur from re-triggering + setTimeout(() => { + isSubmittingRef.current = false + }, 100) + } + + const handleDelete = async () => { + try { + await actions.remove(item.path) + toast('Moved to trash', 'success') + } catch (err) { + toast('Failed to delete', 'error') + } + } + + const handleCopyPath = () => { + actions.copyPath(item.path) + toast('Path copied', 'success') + } + + const cancelRename = () => { + isSubmittingRef.current = true // Prevent blur from triggering rename + setIsRenaming(false) + setNewName(baseName) // Reset to original name + setTimeout(() => { + isSubmittingRef.current = false + }, 100) + } + + const contextMenuContent = ( + + {isDir && ( + <> + actions.createNote(item.path)}> + + New Note + + actions.createFolder(item.path)}> + + New Folder + + + + )} + + + Copy Path + + + { setNewName(baseName); isSubmittingRef.current = false; setIsRenaming(true) }}> + + Rename + + + + Delete + + + ) + + // Inline rename input + if (isRenaming) { return ( - onSelect(item.path, item.kind)} - > - - {item.name} - +
+ {isDir ? : } + setNewName(e.target.value)} + onKeyDown={async (e) => { + e.stopPropagation() + if (e.key === 'Enter') { + e.preventDefault() + await handleRename() + } else if (e.key === 'Escape') { + e.preventDefault() + cancelRename() + } + }} + onBlur={() => { + // Only trigger rename if not already submitting + if (!isSubmittingRef.current) { + handleRename() + } + }} + className="h-6 text-sm flex-1" + autoFocus + /> +
) } + if (!isDir) { + return ( + + + + onSelect(item.path, item.kind)} + > + + {item.name} + + + + {contextMenuContent} + + ) + } + return ( - - onSelect(item.path, item.kind)} - className="group/collapsible [&[data-state=open]>button>svg:first-child]:rotate-90" - > - - - - - {item.name} - - - - - {item.children!.map((subItem, index) => ( - - ))} - - - - + + + + onSelect(item.path, item.kind)} + className="group/collapsible [&[data-state=open]>button>svg:first-child]:rotate-90" + > + + + + + {item.name} + + + + + {(item.children ?? []).map((subItem, index) => ( + + ))} + + + + + + {contextMenuContent} + ) } diff --git a/apps/x/apps/renderer/src/components/ui/context-menu.tsx b/apps/x/apps/renderer/src/components/ui/context-menu.tsx new file mode 100644 index 00000000..624c588a --- /dev/null +++ b/apps/x/apps/renderer/src/components/ui/context-menu.tsx @@ -0,0 +1,250 @@ +import * as React from "react" +import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function ContextMenu({ + ...props +}: React.ComponentProps) { + return +} + +function ContextMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function ContextMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function ContextMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function ContextMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function ContextMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function ContextMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function ContextMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function ContextMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, + ContextMenuCheckboxItem, + ContextMenuRadioItem, + ContextMenuLabel, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuGroup, + ContextMenuPortal, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuRadioGroup, +} diff --git a/apps/x/apps/renderer/src/components/ui/popover.tsx b/apps/x/apps/renderer/src/components/ui/popover.tsx new file mode 100644 index 00000000..6d51b6ce --- /dev/null +++ b/apps/x/apps/renderer/src/components/ui/popover.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +function Popover({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverContent({ + className, + align = "center", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function PopoverAnchor({ + ...props +}: React.ComponentProps) { + return +} + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/apps/x/packages/core/src/config/config.ts b/apps/x/packages/core/src/config/config.ts index 94355b54..c6a15b65 100644 --- a/apps/x/packages/core/src/config/config.ts +++ b/apps/x/packages/core/src/config/config.ts @@ -10,6 +10,7 @@ function ensureDirs() { ensure(WorkDir); ensure(path.join(WorkDir, "agents")); ensure(path.join(WorkDir, "config")); + ensure(path.join(WorkDir, "knowledge")); } ensureDirs(); \ No newline at end of file diff --git a/apps/x/packages/core/src/workspace/workspace.ts b/apps/x/packages/core/src/workspace/workspace.ts index f783d8d5..59910bdb 100644 --- a/apps/x/packages/core/src/workspace/workspace.ts +++ b/apps/x/packages/core/src/workspace/workspace.ts @@ -95,7 +95,7 @@ export async function ensureWorkspaceRoot(): Promise { export async function getRoot(): Promise<{ root: string }> { await ensureWorkspaceRoot(); - return { root: '' }; + return { root: WorkDir }; } export async function exists(relPath: string): Promise<{ exists: boolean }> { @@ -284,10 +284,25 @@ export async function rename( const fromPath = resolveWorkspacePath(from); const toPath = resolveWorkspacePath(to); - // Check if destination exists + // Check if source exists + await fs.access(fromPath); + + // Check if destination exists (only if overwrite is false) if (!overwrite) { - await fs.access(toPath); - throw new Error('Destination already exists'); + try { + await fs.access(toPath); + // If we get here, destination exists + throw new Error('Destination already exists'); + } catch (err: unknown) { + // ENOENT means destination doesn't exist, which is what we want + if (err && typeof err === 'object' && 'code' in err && err.code !== 'ENOENT') { + throw err; + } + // If it's "Destination already exists", re-throw it + if (err instanceof Error && err.message === 'Destination already exists') { + throw err; + } + } } // Create parent directory for destination diff --git a/apps/x/pnpm-lock.yaml b/apps/x/pnpm-lock.yaml index ddd8b282..3a612db3 100644 --- a/apps/x/pnpm-lock.yaml +++ b/apps/x/pnpm-lock.yaml @@ -82,6 +82,9 @@ importers: '@radix-ui/react-collapsible': specifier: ^1.1.12 version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-context-menu': + specifier: ^2.2.16 + version: 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@radix-ui/react-dialog': specifier: ^1.1.15 version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -94,6 +97,9 @@ importers: '@radix-ui/react-label': specifier: ^2.1.8 version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@radix-ui/react-progress': specifier: ^1.1.8 version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -971,6 +977,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-context@1.1.2': resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} peerDependencies: @@ -1107,6 +1126,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.8': resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: @@ -4894,6 +4926,20 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-context@1.1.2(@types/react@19.2.7)(react@19.2.3)': dependencies: react: 19.2.3 @@ -5038,6 +5084,29 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3) + aria-hidden: 1.2.6 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)