{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "file-manager",
  "title": "File Manager",
  "author": "ilinxa",
  "description": "Finder-style file browser — grid and list views, marquee multi-select, cut copy paste, drag-and-drop, and a shared clipboard primitive.",
  "dependencies": [
    "@tanstack/react-virtual",
    "lucide-react"
  ],
  "registryDependencies": [
    "alert-dialog",
    "button",
    "context-menu",
    "dropdown-menu",
    "input",
    "tooltip",
    "@ilinxa/file-clipboard"
  ],
  "files": [
    {
      "path": "src/registry/components/navigation/file-manager/file-manager.tsx",
      "content": "\"use client\";\n\nimport {\n  forwardRef,\n  useCallback,\n  useEffect,\n  useImperativeHandle,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport type { MouseEvent } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport {\n  DEFAULT_FILE_MANAGER_LABELS,\n  type FileManagerActions,\n  type FileManagerHandle,\n  type FileManagerItem,\n  type FileManagerProps,\n  type FileManagerState,\n  type FileManagerToolbarContext,\n  type FsNode,\n} from \"./types\";\nimport {\n  FileManagerContext,\n  type FileManagerContextValue,\n} from \"./hooks/use-file-manager-context\";\nimport { useCurrentFolder } from \"./hooks/use-current-folder\";\nimport { useSelection } from \"./hooks/use-selection\";\nimport { useClipboard } from \"./hooks/use-clipboard\";\nimport { useSortSearch } from \"./hooks/use-sort-search\";\nimport { useVisibleItems } from \"./hooks/use-visible-items\";\nimport { useLazyLoad } from \"./hooks/use-lazy-load\";\nimport { useDrag } from \"./hooks/use-drag\";\nimport { useKeyboard } from \"./hooks/use-keyboard\";\nimport { useMarquee } from \"./hooks/use-marquee\";\nimport {\n  countAllNodes,\n  findNode,\n  indexNodes,\n} from \"./lib/tree-utils\";\nimport { validateNodes } from \"./lib/validation\";\nimport { FileManagerToolbar } from \"./parts/file-manager-toolbar\";\nimport { FileManagerContentPane } from \"./parts/file-manager-content-pane\";\nimport { FileManagerStatusBar } from \"./parts/file-manager-status-bar\";\nimport { FileManagerDragOverlay } from \"./parts/file-manager-drag-overlay\";\nimport { FileManagerDeleteConfirm } from \"./parts/file-manager-delete-confirm\";\nimport { FileManagerContextMenu } from \"./parts/file-manager-context-menu\";\n\nconst LIST_ROW_HEIGHT = 32;\n\nexport const FileManager = forwardRef<\n  FileManagerHandle,\n  FileManagerProps\n>(function FileManager(props, ref) {\n  const {\n    nodes,\n    currentFolderId: controlledFolderId,\n    defaultCurrentFolderId,\n    onCurrentFolderChange,\n    onLoadChildren,\n    selectedIds,\n    defaultSelectedIds,\n    onSelectedChange,\n    preserveSelectionOnNavigate = false,\n    clipboard,\n    defaultClipboard,\n    onClipboardChange,\n    onOpen,\n    onCreate,\n    onRename,\n    onDelete,\n    onMove,\n    onPaste,\n    onRefresh,\n    onExternalDrop,\n    validateRename,\n    viewMode,\n    defaultViewMode,\n    onViewModeChange,\n    iconSize,\n    defaultIconSize,\n    onIconSizeChange,\n    sort,\n    defaultSort,\n    onSortChange,\n    sortItems,\n    searchQuery,\n    defaultSearchQuery,\n    onSearchQueryChange,\n    filterItems,\n    showHidden = false,\n    isHidden,\n    enableHistory = true,\n    historyBackIds,\n    historyForwardIds,\n    onPathTyped,\n    iconForNode,\n    renderItem,\n    renderToolbar,\n    renderContextMenu,\n    renderEmpty,\n    renderLoading,\n    renderStatusBar,\n    renderDeleteConfirm,\n    sidebar,\n    details,\n    toolbar = true,\n    title,\n    showNewFile = true,\n    showNewFolder = true,\n    showRefresh = true,\n    showSearch = true,\n    showViewToggle = true,\n    showIconSize = true,\n    showSort = true,\n    showBackForward = true,\n    showUpButton = true,\n    showPathBar = true,\n    showStatusBar = true,\n    contextMenu = true,\n    contextMenuActions,\n    confirmDelete = true,\n    enableInternalDrag = true,\n    enableExternalDrop = true,\n    enableMarqueeSelection = true,\n    virtualize = \"auto\",\n    virtualizeThreshold = 200,\n    className,\n    style,\n    labels: labelsOverride,\n  } = props;\n\n  // Merge labels\n  const labels = useMemo(\n    () => ({ ...DEFAULT_FILE_MANAGER_LABELS, ...labelsOverride }),\n    [labelsOverride],\n  );\n\n  // Index nodes once per `nodes` change\n  const nodeIndex = useMemo(() => indexNodes(nodes), [nodes]);\n\n  // Validate nodes (dev only)\n  useEffect(() => {\n    if (process.env.NODE_ENV === \"production\") return;\n    const warnings = validateNodes(nodes);\n    for (const w of warnings) {\n      console.warn(\"[file-manager]\", w);\n    }\n  }, [nodes]);\n\n  // Pending delete state\n  const [pendingDeleteIds, setPendingDeleteIds] = useState<string[]>([]);\n\n  // Sort + search + view mode + icon size\n  const sortSearch = useSortSearch({\n    controlledSort: sort,\n    defaultSort,\n    onSortChange,\n    controlledSearch: searchQuery,\n    defaultSearch: defaultSearchQuery,\n    onSearchChange: onSearchQueryChange,\n    controlledViewMode: viewMode,\n    defaultViewMode,\n    onViewModeChange,\n    controlledIconSize: iconSize,\n    defaultIconSize,\n    onIconSizeChange,\n  });\n\n  // Current folder + history\n  const folder = useCurrentFolder({\n    nodes,\n    index: nodeIndex,\n    controlledFolderId,\n    defaultFolderId: defaultCurrentFolderId,\n    onCurrentFolderChange,\n    enableHistory,\n    controlledBack: historyBackIds,\n    controlledForward: historyForwardIds,\n  });\n\n  // Clipboard\n  const clipboardHook = useClipboard({\n    controlledClipboard: clipboard,\n    defaultClipboard,\n    onClipboardChange,\n  });\n\n  // Selection\n  const [focusedId, setFocusedId] = useState<string | null>(null);\n  const [renamingId, setRenamingId] = useState<string | null>(null);\n\n  const selection = useSelection({\n    controlledIds: selectedIds,\n    defaultIds: defaultSelectedIds,\n    onSelectedChange,\n    index: nodeIndex,\n  });\n\n  // Lazy load\n  const lazy = useLazyLoad({\n    nodes,\n    currentFolderId: folder.currentFolderId,\n    index: nodeIndex,\n    onLoadChildren,\n  });\n\n  // Visible items (filter + sort + flatten)\n  const { items, totalSize, visibleCount } = useVisibleItems({\n    nodes,\n    index: nodeIndex,\n    currentFolderId: folder.currentFolderId,\n    selectedIds: selection.selectedIds,\n    focusedId,\n    clipboard: clipboardHook.clipboard,\n    sort: sortSearch.sort,\n    searchQuery: sortSearch.searchQuery,\n    showHidden,\n    isHidden,\n    filterItems,\n    sortItems,\n  });\n\n  // Drag\n  const drag = useDrag({\n    nodes,\n    currentFolderId: folder.currentFolderId,\n    enableInternalDrag,\n    enableExternalDrop,\n    selectedIds: selection.selectedIds,\n    onMove,\n    onExternalDrop,\n    selectIds: (idOrIds) => selection.select(idOrIds),\n  });\n\n  // Refs\n  const rootElRef = useRef<HTMLDivElement>(null);\n  const scrollRef = useRef<HTMLDivElement>(null);\n  const columnsRef = useRef<number>(1);\n\n  // ── Roving tabindex → real DOM focus (v0.1.1, review 5.6) ─────────────\n  // The roving tabindex was state-only: arrow keys updated `focusedId` but\n  // never moved DOM focus — invisible to screen readers, dead focus ring.\n  // Mirrors app-sidebar's pattern; only steals focus while the user is\n  // already interacting inside the component.\n  useEffect(() => {\n    if (!focusedId) return;\n    if (typeof document === \"undefined\") return;\n    const root = rootElRef.current;\n    if (!root) return;\n    if (!root.contains(document.activeElement)) return;\n    const escaped = window.CSS.escape(focusedId);\n    const target = root.querySelector<HTMLElement>(\n      `[data-item-id=\"${escaped}\"]`,\n    );\n    if (target) {\n      if (document.activeElement !== target) target.focus();\n    } else {\n      // Virtualized list mode: the focused row may not be mounted — fall\n      // back to the container so keyboard navigation keeps working.\n      root.focus({ preventScroll: true });\n    }\n  }, [focusedId]);\n\n  // Marquee selection\n  const marquee = useMarquee({\n    enabled: enableMarqueeSelection,\n    applySelection: (ids) => {\n      selection.select(ids);\n    },\n    snapshotSelection: () => new Set(selection.selectedIds),\n    containerRef: scrollRef,\n  });\n\n  // ── Actions ────────────────────────────────────────────────────────────\n  const triggerDelete = useCallback(\n    (ids: string[]) => {\n      if (ids.length === 0) return;\n      if (confirmDelete) {\n        setPendingDeleteIds(ids);\n        return;\n      }\n      onDelete?.({ ids });\n    },\n    [confirmDelete, onDelete],\n  );\n\n  const triggerOpen = useCallback(\n    (id: string) => {\n      const node = findNode(id, nodes);\n      if (!node) return;\n      if (node.type === \"folder\") {\n        if (!preserveSelectionOnNavigate) selection.clearSelection();\n        folder.navigateTo(id);\n      } else {\n        onOpen?.({ node });\n      }\n    },\n    [nodes, folder, onOpen, preserveSelectionOnNavigate, selection],\n  );\n\n  const paste = useCallback(() => {\n    const cb = clipboardHook.clipboard;\n    if (!cb.kind || cb.ids.length === 0) return;\n    onPaste?.({\n      ids: cb.ids,\n      kind: cb.kind,\n      targetFolderId: folder.currentFolderId,\n    });\n    if (cb.kind === \"cut\") {\n      clipboardHook.clearClipboard();\n    }\n  }, [clipboardHook, onPaste, folder.currentFolderId]);\n\n  // Wrap navigation to clear selection on navigate (unless preserved).\n  const maybeClearSelection = useCallback(() => {\n    if (!preserveSelectionOnNavigate) selection.clearSelection();\n  }, [preserveSelectionOnNavigate, selection]);\n\n  const navigateTo = useCallback(\n    (id: string | null) => {\n      maybeClearSelection();\n      folder.navigateTo(id);\n    },\n    [maybeClearSelection, folder],\n  );\n  const navigateUp = useCallback(() => {\n    maybeClearSelection();\n    folder.navigateUp();\n  }, [maybeClearSelection, folder]);\n  const navigateBack = useCallback(() => {\n    maybeClearSelection();\n    folder.navigateBack();\n  }, [maybeClearSelection, folder]);\n  const navigateForward = useCallback(() => {\n    maybeClearSelection();\n    folder.navigateForward();\n  }, [maybeClearSelection, folder]);\n\n  const actions: FileManagerActions = useMemo(\n    () => ({\n      navigateTo,\n      navigateUp,\n      navigateBack,\n      navigateForward,\n      refresh: (nodeId = null) => onRefresh?.({ nodeId }),\n      setViewMode: sortSearch.setViewMode,\n      setIconSize: sortSearch.setIconSize,\n      setSort: sortSearch.setSort,\n      setSearchQuery: sortSearch.setSearchQuery,\n      select: selection.select,\n      toggleSelection: selection.toggleSelection,\n      clearSelection: selection.clearSelection,\n      selectAll: () =>\n        selection.selectAll(items.map((i) => i.node.id)),\n      focusNode: (id) => setFocusedId(id),\n      startRename: (id) => setRenamingId(id),\n      cancelRename: () => setRenamingId(null),\n      triggerCreate: (type) =>\n        onCreate?.({ parentId: folder.currentFolderId, type }),\n      triggerDelete,\n      triggerOpen,\n      cut: clipboardHook.cut,\n      copy: clipboardHook.copy,\n      paste,\n      clearClipboard: clipboardHook.clearClipboard,\n    }),\n    [\n      navigateTo,\n      navigateUp,\n      navigateBack,\n      navigateForward,\n      folder.currentFolderId,\n      sortSearch,\n      selection,\n      items,\n      onRefresh,\n      onCreate,\n      triggerDelete,\n      triggerOpen,\n      clipboardHook,\n      paste,\n    ],\n  );\n\n  // ── State ──────────────────────────────────────────────────────────────\n  const state: FileManagerState = useMemo(\n    () => ({\n      currentFolderId: folder.currentFolderId,\n      selectedIds: selection.selectedIds,\n      focusedId,\n      renamingId,\n      viewMode: sortSearch.viewMode,\n      iconSize: sortSearch.iconSize,\n      sort: sortSearch.sort,\n      searchQuery: sortSearch.searchQuery,\n      path: folder.path,\n      loadingChildren: lazy.loadingChildren,\n      loadError: lazy.loadError,\n      clipboard: clipboardHook.clipboard,\n      historyBackIds: folder.historyBackIds,\n      historyForwardIds: folder.historyForwardIds,\n    }),\n    [\n      folder.currentFolderId,\n      folder.path,\n      folder.historyBackIds,\n      folder.historyForwardIds,\n      selection.selectedIds,\n      focusedId,\n      renamingId,\n      sortSearch.viewMode,\n      sortSearch.iconSize,\n      sortSearch.sort,\n      sortSearch.searchQuery,\n      lazy.loadingChildren,\n      lazy.loadError,\n      clipboardHook.clipboard,\n    ],\n  );\n\n  // Imperative handle\n  useImperativeHandle(\n    ref,\n    () => ({ state, actions }),\n    [state, actions],\n  );\n\n  // Keyboard\n  const kb = useKeyboard({\n    items,\n    focusedId,\n    renamingId,\n    selectedIds: selection.selectedIds,\n    viewMode: sortSearch.viewMode,\n    columnsRef,\n    actions,\n    onOpen,\n  });\n\n  // ── Item callbacks (click / dblclick / contextmenu) ───────────────────\n  const onItemClick = useCallback(\n    (e: MouseEvent, item: FileManagerItem) => {\n      if (e.metaKey || e.ctrlKey) {\n        selection.toggleSelection(item.node.id);\n        selection.setSelectionAnchorId(item.node.id);\n      } else if (e.shiftKey) {\n        const anchor = selection.getSelectionAnchorId();\n        if (!anchor) {\n          selection.select(item.node.id);\n          selection.setSelectionAnchorId(item.node.id);\n        } else {\n          const aIdx = items.findIndex((i) => i.node.id === anchor);\n          const bIdx = items.findIndex((i) => i.node.id === item.node.id);\n          if (aIdx !== -1 && bIdx !== -1) {\n            const [from, to] = aIdx < bIdx ? [aIdx, bIdx] : [bIdx, aIdx];\n            const ids = items.slice(from, to + 1).map((i) => i.node.id);\n            selection.select(ids);\n          }\n        }\n      } else {\n        selection.select(item.node.id);\n        selection.setSelectionAnchorId(item.node.id);\n      }\n      setFocusedId(item.node.id);\n    },\n    [items, selection],\n  );\n\n  const onItemDoubleClick = useCallback(\n    (_e: MouseEvent, item: FileManagerItem) => {\n      if (item.node.type === \"folder\") {\n        folder.navigateTo(item.node.id);\n      } else {\n        onOpen?.({ node: item.node });\n      }\n    },\n    [folder, onOpen],\n  );\n\n  const onItemContextMenu = useCallback(\n    (_e: MouseEvent, item: FileManagerItem) => {\n      if (!selection.selectedIds.has(item.node.id)) {\n        selection.select(item.node.id);\n      }\n      setFocusedId(item.node.id);\n    },\n    [selection],\n  );\n\n  const onRenameCommit = useCallback(\n    (id: string, nextName: string) => {\n      const node = findNode(id, nodes);\n      if (node && onRename) onRename({ id, node, nextName });\n      setRenamingId(null);\n    },\n    [nodes, onRename],\n  );\n\n  const onRenameCancel = useCallback(() => setRenamingId(null), []);\n\n  // ── Header / toolbar context ──────────────────────────────────────────\n  const headerCtx: FileManagerToolbarContext = useMemo(\n    () => ({\n      state,\n      actions,\n      showNewFile,\n      showNewFolder,\n      showRefresh,\n      showSearch,\n      showViewToggle,\n      showIconSize,\n      showSort,\n      showBackForward,\n      showUpButton,\n      showPathBar,\n      title,\n      labels,\n    }),\n    [\n      state,\n      actions,\n      showNewFile,\n      showNewFolder,\n      showRefresh,\n      showSearch,\n      showViewToggle,\n      showIconSize,\n      showSort,\n      showBackForward,\n      showUpButton,\n      showPathBar,\n      title,\n      labels,\n    ],\n  );\n\n  // ── Pending delete dialog ─────────────────────────────────────────────\n  const pendingNodes: FsNode[] = useMemo(\n    () =>\n      pendingDeleteIds\n        .map((id) => nodeIndex.get(id))\n        .filter((n): n is FsNode => Boolean(n)),\n    [pendingDeleteIds, nodeIndex],\n  );\n  const onConfirmDelete = useCallback(() => {\n    if (pendingDeleteIds.length === 0) return;\n    onDelete?.({ ids: pendingDeleteIds });\n    setPendingDeleteIds([]);\n  }, [onDelete, pendingDeleteIds]);\n  const onCancelDelete = useCallback(() => {\n    setPendingDeleteIds([]);\n  }, []);\n\n  // ── Public Context value ──────────────────────────────────────────────\n  const totalCount = useMemo(() => countAllNodes(nodes), [nodes]);\n  const ctxValue: FileManagerContextValue = useMemo(\n    () => ({\n      state,\n      actions,\n      items,\n      totalCount,\n      visibleCount,\n      totalSize,\n      showNewFile,\n      showNewFolder,\n      showRefresh,\n      showSearch,\n      showViewToggle,\n      showIconSize,\n      showSort,\n      showBackForward,\n      showUpButton,\n      showPathBar,\n      showStatusBar,\n      title,\n      labels,\n    }),\n    [\n      state,\n      actions,\n      items,\n      totalCount,\n      visibleCount,\n      totalSize,\n      showNewFile,\n      showNewFolder,\n      showRefresh,\n      showSearch,\n      showViewToggle,\n      showIconSize,\n      showSort,\n      showBackForward,\n      showUpButton,\n      showPathBar,\n      showStatusBar,\n      title,\n      labels,\n    ],\n  );\n\n  // ── Render ────────────────────────────────────────────────────────────\n  const isEmpty =\n    !lazy.loadingChildren && !lazy.loadError && items.length === 0;\n\n  const contextMenuGates = {\n    open: contextMenuActions?.open ?? true,\n    newFile: contextMenuActions?.newFile ?? true,\n    newFolder: contextMenuActions?.newFolder ?? true,\n    cut: contextMenuActions?.cut ?? true,\n    copy: contextMenuActions?.copy ?? true,\n    paste: contextMenuActions?.paste ?? true,\n    rename: contextMenuActions?.rename ?? true,\n    delete: contextMenuActions?.delete ?? true,\n    refresh: contextMenuActions?.refresh ?? true,\n  };\n  const wired = {\n    onOpen: !!onOpen,\n    onCreate: !!onCreate,\n    onRename: !!onRename,\n    onDelete: !!onDelete,\n    onMove: !!onMove,\n    onPaste: !!onPaste,\n    onRefresh: !!onRefresh,\n  };\n\n  return (\n    <FileManagerContext.Provider value={ctxValue}>\n      {/* No delayDuration — Radix-only prop; Base UI's TooltipProvider rejects it (F-cross-13). */}\n      <TooltipProvider>\n        <div\n          ref={rootElRef}\n          role=\"region\"\n          aria-label={title ?? labels.title}\n          tabIndex={focusedId === null ? 0 : -1}\n          className={cn(\n            \"relative flex h-full w-full overflow-hidden rounded-md border border-border/60 bg-background text-foreground outline-none\",\n            \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n            className,\n          )}\n          style={style}\n          onKeyDown={kb.onKeyDown}\n        >\n          {sidebar ? (\n            <aside className=\"flex w-64 shrink-0 flex-col border-r border-border/60 bg-card/30\">\n              {sidebar}\n            </aside>\n          ) : null}\n          <div className=\"flex min-w-0 flex-1 flex-col\">\n            {toolbar\n              ? renderToolbar\n                ? renderToolbar(headerCtx)\n                : <FileManagerToolbar onPathTyped={onPathTyped} />\n              : null}\n            <FileManagerContextMenu\n              state={state}\n              actions={actions}\n              enabled={contextMenu}\n              labels={labels}\n              gates={contextMenuGates}\n              wired={wired}\n              nodeIndex={nodeIndex}\n              renderContextMenu={renderContextMenu}\n            >\n              <FileManagerContentPane\n                items={items}\n                viewMode={sortSearch.viewMode}\n                iconSize={sortSearch.iconSize}\n                rowHeight={LIST_ROW_HEIGHT}\n                sort={sortSearch.sort}\n                onSetSort={sortSearch.setSort}\n                renamingId={renamingId}\n                validateRename={validateRename}\n                iconForNode={iconForNode}\n                enableInternalDrag={enableInternalDrag}\n                enableMarqueeSelection={enableMarqueeSelection}\n                dropTargetId={drag.dragOver?.itemId ?? null}\n                dropInvalid={drag.dragOver ? !drag.dragOver.valid : false}\n                loading={lazy.loadingChildren}\n                loadError={lazy.loadError}\n                onRetryLoad={lazy.retry}\n                isEmpty={isEmpty}\n                marqueeRect={marquee.rect}\n                marqueeActive={marquee.active}\n                onMarqueePointerDown={marquee.onPointerDown}\n                scrollRef={scrollRef}\n                columnsRef={columnsRef}\n                virtualize={virtualize}\n                virtualizeThreshold={virtualizeThreshold}\n                renderItem={renderItem}\n                renderEmpty={\n                  renderEmpty\n                    ? () =>\n                        renderEmpty({\n                          actions,\n                          showNewFile,\n                          showNewFolder,\n                          labels,\n                        })\n                    : undefined\n                }\n                renderLoading={renderLoading}\n                labels={labels}\n                onItemClick={onItemClick}\n                onItemDoubleClick={onItemDoubleClick}\n                onItemContextMenu={onItemContextMenu}\n                onRenameCommit={onRenameCommit}\n                onRenameCancel={onRenameCancel}\n                onItemDragStart={drag.onItemDragStart}\n                onItemDragOver={drag.onItemDragOver}\n                onItemDragLeave={drag.onItemDragLeave}\n                onItemDrop={drag.onItemDrop}\n                onItemDragEnd={drag.onItemDragEnd}\n                onContainerDragEnter={drag.onContainerDragEnter}\n                onContainerDragOver={drag.onContainerDragOver}\n                onContainerDragLeave={drag.onContainerDragLeave}\n                onContainerDrop={drag.onContainerDrop}\n              />\n            </FileManagerContextMenu>\n            {showStatusBar\n              ? renderStatusBar\n                ? renderStatusBar({\n                    state,\n                    actions,\n                    totalCount,\n                    selectedCount: selection.selectedIds.size,\n                    totalSize,\n                    labels,\n                  })\n                : <FileManagerStatusBar />\n              : null}\n          </div>\n          {details ? (\n            <aside className=\"flex w-72 min-w-70 shrink-0 flex-col border-l border-border/60 bg-card/30\">\n              {details}\n            </aside>\n          ) : null}\n          <FileManagerDragOverlay\n            visible={drag.isExternalDragging}\n            labels={labels}\n          />\n          {confirmDelete && pendingDeleteIds.length > 0 ? (\n            renderDeleteConfirm ? (\n              renderDeleteConfirm({\n                ids: pendingDeleteIds,\n                nodes: pendingNodes,\n                onConfirm: onConfirmDelete,\n                onCancel: onCancelDelete,\n                labels,\n              })\n            ) : (\n              <FileManagerDeleteConfirm\n                open={true}\n                ids={pendingDeleteIds}\n                nodes={pendingNodes}\n                labels={labels}\n                onConfirm={onConfirmDelete}\n                onCancel={onCancelDelete}\n              />\n            )\n          ) : null}\n        </div>\n      </TooltipProvider>\n    </FileManagerContext.Provider>\n  );\n});\n\nFileManager.displayName = \"FileManager\";\n",
      "type": "registry:component",
      "target": "components/file-manager/file-manager.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/index.ts",
      "content": "export { FileManager } from \"./file-manager\";\nexport { FileManagerToolbar } from \"./parts/file-manager-toolbar\";\nexport { FileManagerPathBar } from \"./parts/file-manager-path-bar\";\nexport { FileManagerViewToggle } from \"./parts/file-manager-view-toggle\";\nexport { FileManagerIconSizeControl } from \"./parts/file-manager-icon-size-control\";\nexport { FileManagerSortMenu } from \"./parts/file-manager-sort-menu\";\nexport { FileManagerSearchInput } from \"./parts/file-manager-search-input\";\nexport { FileManagerStatusBar } from \"./parts/file-manager-status-bar\";\nexport { FileManagerBackForward } from \"./parts/file-manager-back-forward\";\nexport { FileManagerUpButton } from \"./parts/file-manager-up-button\";\nexport { FileManagerNewButtons } from \"./parts/file-manager-new-buttons\";\nexport { FileManagerRefreshButton } from \"./parts/file-manager-refresh-button\";\nexport { useFileManager } from \"./hooks/use-file-manager-context\";\nexport { mergeLoadedChildren } from \"./lib/tree-utils\";\nexport { iconForExtension } from \"./lib/icons\";\nexport { DEFAULT_FILE_MANAGER_LABELS } from \"./types\";\n\n// Shared clipboard primitive (sibling _shared/ module). Re-exported for\n// convenience so consumers don't need to import from a non-component path.\nexport {\n  FileClipboardProvider,\n  useFileClipboard,\n  EMPTY_CLIPBOARD,\n} from \"../_shared/file-clipboard\";\n\nexport type {\n  FsNode,\n  FsNodeType,\n  FileManagerProps,\n  FileManagerHandle,\n  FileManagerState,\n  FileManagerActions,\n  FileManagerItem,\n  FileManagerViewMode,\n  FileManagerIconSize,\n  FileManagerSortKey,\n  FileManagerSortOrder,\n  FileManagerSortState,\n  FileManagerDropPosition,\n  FileManagerVirtualizeMode,\n  FileClipboard,\n  FileClipboardKind,\n  FileManagerOpenArgs,\n  FileManagerCurrentFolderChangeArgs,\n  FileManagerCreateArgs,\n  FileManagerRenameArgs,\n  FileManagerDeleteArgs,\n  FileManagerMoveArgs,\n  FileManagerPasteArgs,\n  FileManagerClipboardChangeArgs,\n  FileManagerRefreshArgs,\n  FileManagerExternalDropArgs,\n  FileManagerLoadChildrenArgs,\n  FileManagerSelectionChangeArgs,\n  FileManagerSortChangeArgs,\n  FileManagerViewModeChangeArgs,\n  FileManagerIconSizeChangeArgs,\n  FileManagerSearchChangeArgs,\n  FileManagerPathTypedArgs,\n  FileManagerValidateRenameArgs,\n  FileManagerIconForNodeArgs,\n  FileManagerContextMenuItem,\n  FileManagerContextMenuItemId,\n  FileManagerToolbarContext,\n  FileManagerStatusBarContext,\n  FileManagerContextMenuContext,\n  FileManagerEmptyContext,\n  FileManagerItemRenderArgs,\n  FileManagerDeleteConfirmContext,\n  FileManagerLabels,\n} from \"./types\";\n",
      "type": "registry:component",
      "target": "components/file-manager/index.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/types.ts",
      "content": "import type { CSSProperties, ReactNode, Ref } from \"react\";\n\nimport type {\n  FileClipboard,\n  FileClipboardKind,\n} from \"../_shared/file-clipboard\";\n\nexport type { FileClipboard, FileClipboardKind };\n\nexport type FsNodeType = \"file\" | \"folder\";\n\n/** Hierarchical-node shape — compatible-by-convention with file-tree's FsNode. */\nexport interface FsNode {\n  id: string;\n  name: string;\n  type: FsNodeType;\n  parentId?: string | null;\n  children?: FsNode[];\n  ext?: string;\n  size?: number;\n  modifiedAt?: string;\n  icon?: ReactNode;\n  meta?: Record<string, unknown>;\n}\n\nexport type FileManagerViewMode = \"grid\" | \"list\";\nexport type FileManagerIconSize = \"sm\" | \"md\" | \"lg\";\nexport type FileManagerSortKey = \"name\" | \"modified\" | \"size\" | \"type\";\nexport type FileManagerSortOrder = \"asc\" | \"desc\";\nexport type FileManagerDropPosition = \"inside\";\nexport type FileManagerVirtualizeMode = \"auto\" | \"always\" | \"never\";\n\nexport interface FileManagerSortState {\n  key: FileManagerSortKey;\n  order: FileManagerSortOrder;\n}\n\n/** Visible-item descriptor produced for each visible cell/row. */\nexport interface FileManagerItem {\n  node: FsNode;\n  selected: boolean;\n  focused: boolean;\n  cut: boolean;\n  index: number;\n  totalVisible: number;\n}\n\n/** Read-only state surfaced to slots and the imperative ref. */\nexport interface FileManagerState {\n  currentFolderId: string | null;\n  selectedIds: ReadonlySet<string>;\n  focusedId: string | null;\n  renamingId: string | null;\n  viewMode: FileManagerViewMode;\n  iconSize: FileManagerIconSize;\n  sort: FileManagerSortState;\n  searchQuery: string;\n  /** Path from root to the current folder (root → ... → current). Empty when at root. */\n  path: FsNode[];\n  loadingChildren: boolean;\n  loadError: string | null;\n  clipboard: FileClipboard;\n  historyBackIds: string[];\n  historyForwardIds: string[];\n}\n\n/** Imperative actions exposed to slots and the imperative ref. */\nexport interface FileManagerActions {\n  navigateTo: (folderId: string | null) => void;\n  navigateUp: () => void;\n  navigateBack: () => void;\n  navigateForward: () => void;\n  refresh: (nodeId?: string | null) => void;\n\n  setViewMode: (mode: FileManagerViewMode) => void;\n  setIconSize: (size: FileManagerIconSize) => void;\n  setSort: (sort: FileManagerSortState) => void;\n  setSearchQuery: (query: string) => void;\n\n  select: (idOrIds: string | string[]) => void;\n  toggleSelection: (id: string) => void;\n  clearSelection: () => void;\n  selectAll: () => void;\n  focusNode: (id: string) => void;\n\n  startRename: (id: string) => void;\n  cancelRename: () => void;\n\n  triggerCreate: (type: FsNodeType) => void;\n  triggerDelete: (ids: string[]) => void;\n  triggerOpen: (id: string) => void;\n\n  cut: (ids: string[]) => void;\n  copy: (ids: string[]) => void;\n  paste: () => void;\n  clearClipboard: () => void;\n}\n\nexport interface FileManagerHandle {\n  state: FileManagerState;\n  actions: FileManagerActions;\n}\n\n// ─── Callback arg shapes (object-shape per F-cross-12) ─────────────────────\n\nexport interface FileManagerOpenArgs {\n  node: FsNode;\n}\nexport interface FileManagerCurrentFolderChangeArgs {\n  folderId: string | null;\n}\nexport interface FileManagerCreateArgs {\n  parentId: string | null;\n  type: FsNodeType;\n}\nexport interface FileManagerRenameArgs {\n  id: string;\n  node: FsNode;\n  nextName: string;\n}\nexport interface FileManagerDeleteArgs {\n  ids: string[];\n}\nexport interface FileManagerMoveArgs {\n  ids: string[];\n  targetId: string | null;\n  position: FileManagerDropPosition;\n}\nexport interface FileManagerPasteArgs {\n  ids: string[];\n  kind: FileClipboardKind;\n  targetFolderId: string | null;\n}\nexport interface FileManagerClipboardChangeArgs {\n  clipboard: FileClipboard;\n}\nexport interface FileManagerRefreshArgs {\n  nodeId: string | null;\n}\nexport interface FileManagerExternalDropArgs {\n  files: File[];\n  targetFolderId: string | null;\n}\nexport interface FileManagerLoadChildrenArgs {\n  nodeId: string;\n  node: FsNode;\n}\nexport interface FileManagerSelectionChangeArgs {\n  ids: Set<string>;\n}\nexport interface FileManagerSortChangeArgs {\n  sort: FileManagerSortState;\n}\nexport interface FileManagerViewModeChangeArgs {\n  mode: FileManagerViewMode;\n}\nexport interface FileManagerIconSizeChangeArgs {\n  size: FileManagerIconSize;\n}\nexport interface FileManagerSearchChangeArgs {\n  query: string;\n}\nexport interface FileManagerPathTypedArgs {\n  path: string;\n}\nexport interface FileManagerValidateRenameArgs {\n  node: FsNode;\n  nextName: string;\n}\nexport interface FileManagerIconForNodeArgs {\n  node: FsNode;\n}\n\n// ─── Default labels ─────────────────────────────────────────────────────────\n\nexport interface FileManagerLabels {\n  title: string;\n  back: string;\n  forward: string;\n  up: string;\n  refresh: string;\n  newFile: string;\n  newFolder: string;\n  searchPlaceholder: string;\n  viewGrid: string;\n  viewList: string;\n  iconSizeSmall: string;\n  iconSizeMedium: string;\n  iconSizeLarge: string;\n  sortByName: string;\n  sortByModified: string;\n  sortBySize: string;\n  sortByType: string;\n  sortAsc: string;\n  sortDesc: string;\n  contextOpen: string;\n  contextNewFile: string;\n  contextNewFolder: string;\n  contextCut: string;\n  contextCopy: string;\n  contextPaste: string;\n  contextRename: string;\n  contextDelete: string;\n  contextRefresh: string;\n  deleteConfirmTitle: string;\n  deleteConfirmDescription: string;\n  deleteConfirmAction: string;\n  deleteConfirmCancel: string;\n  emptyTitle: string;\n  loading: string;\n  loadError: string;\n  retry: string;\n  externalDropOverlay: string;\n  itemCount: string;\n  itemCountSelected: string;\n  totalSize: string;\n  pathRoot: string;\n  columnName: string;\n  columnModified: string;\n  columnSize: string;\n  columnType: string;\n}\n\n// ─── Slot contexts ──────────────────────────────────────────────────────────\n\nexport interface FileManagerToolbarContext {\n  state: FileManagerState;\n  actions: FileManagerActions;\n  showNewFile: boolean;\n  showNewFolder: boolean;\n  showRefresh: boolean;\n  showSearch: boolean;\n  showViewToggle: boolean;\n  showIconSize: boolean;\n  showSort: boolean;\n  showBackForward: boolean;\n  showUpButton: boolean;\n  showPathBar: boolean;\n  title?: string;\n  labels: FileManagerLabels;\n}\n\nexport interface FileManagerStatusBarContext {\n  state: FileManagerState;\n  actions: FileManagerActions;\n  totalCount: number;\n  selectedCount: number;\n  totalSize: number;\n  labels: FileManagerLabels;\n}\n\nexport type FileManagerContextMenuItemId =\n  | \"open\"\n  | \"new-file\"\n  | \"new-folder\"\n  | \"cut\"\n  | \"copy\"\n  | \"paste\"\n  | \"rename\"\n  | \"delete\"\n  | \"refresh\";\n\nexport interface FileManagerContextMenuItem {\n  id: FileManagerContextMenuItemId;\n  label: string;\n  onSelect: () => void;\n  disabled?: boolean;\n  destructive?: boolean;\n  icon?: ReactNode;\n}\n\nexport interface FileManagerContextMenuContext {\n  state: FileManagerState;\n  actions: FileManagerActions;\n  /** Right-clicked item — `null` when right-clicking empty whitespace. */\n  node: FsNode | null;\n  defaultActions: FileManagerContextMenuItem[];\n  position: { x: number; y: number };\n  labels: FileManagerLabels;\n}\n\nexport interface FileManagerEmptyContext {\n  actions: FileManagerActions;\n  showNewFile: boolean;\n  showNewFolder: boolean;\n  labels: FileManagerLabels;\n}\n\nexport interface FileManagerItemRenderArgs {\n  item: FileManagerItem;\n  defaultItem: ReactNode;\n  viewMode: FileManagerViewMode;\n}\n\nexport interface FileManagerDeleteConfirmContext {\n  ids: string[];\n  nodes: FsNode[];\n  onConfirm: () => void;\n  onCancel: () => void;\n  labels: FileManagerLabels;\n}\n\n// ─── Top-level props ────────────────────────────────────────────────────────\n\nexport interface FileManagerProps {\n  // Data\n  nodes: FsNode[];\n\n  // Current folder\n  currentFolderId?: string | null;\n  defaultCurrentFolderId?: string | null;\n  onCurrentFolderChange?: (\n    args: FileManagerCurrentFolderChangeArgs,\n  ) => void;\n\n  // Lazy load\n  onLoadChildren?: (\n    args: FileManagerLoadChildrenArgs,\n  ) => Promise<FsNode[]>;\n\n  // Selection\n  selectedIds?: Set<string>;\n  defaultSelectedIds?: Set<string>;\n  onSelectedChange?: (args: FileManagerSelectionChangeArgs) => void;\n  preserveSelectionOnNavigate?: boolean;\n\n  // Clipboard\n  clipboard?: FileClipboard;\n  defaultClipboard?: FileClipboard;\n  onClipboardChange?: (args: FileManagerClipboardChangeArgs) => void;\n\n  // Operations\n  onOpen?: (args: FileManagerOpenArgs) => void;\n  onCreate?: (args: FileManagerCreateArgs) => void;\n  onRename?: (args: FileManagerRenameArgs) => void;\n  onDelete?: (args: FileManagerDeleteArgs) => void;\n  onMove?: (args: FileManagerMoveArgs) => void;\n  onPaste?: (args: FileManagerPasteArgs) => void;\n  onRefresh?: (args: FileManagerRefreshArgs) => void;\n  onExternalDrop?: (args: FileManagerExternalDropArgs) => void;\n  validateRename?: (args: FileManagerValidateRenameArgs) => string | null;\n\n  // View mode\n  viewMode?: FileManagerViewMode;\n  defaultViewMode?: FileManagerViewMode;\n  onViewModeChange?: (args: FileManagerViewModeChangeArgs) => void;\n\n  // Icon size\n  iconSize?: FileManagerIconSize;\n  defaultIconSize?: FileManagerIconSize;\n  onIconSizeChange?: (args: FileManagerIconSizeChangeArgs) => void;\n\n  // Sort\n  sort?: FileManagerSortState;\n  defaultSort?: FileManagerSortState;\n  onSortChange?: (args: FileManagerSortChangeArgs) => void;\n  sortItems?: (a: FsNode, b: FsNode, sort: FileManagerSortState) => number;\n\n  // Search / filter\n  searchQuery?: string;\n  defaultSearchQuery?: string;\n  onSearchQueryChange?: (args: FileManagerSearchChangeArgs) => void;\n  filterItems?: (node: FsNode, query: string) => boolean;\n  showHidden?: boolean;\n  isHidden?: (node: FsNode) => boolean;\n\n  // Navigation history\n  enableHistory?: boolean;\n  historyBackIds?: string[];\n  historyForwardIds?: string[];\n  onPathTyped?: (args: FileManagerPathTypedArgs) => void;\n\n  // Rendering\n  iconForNode?: (args: FileManagerIconForNodeArgs) => ReactNode;\n  renderItem?: (args: FileManagerItemRenderArgs) => ReactNode;\n  renderToolbar?: (ctx: FileManagerToolbarContext) => ReactNode;\n  renderContextMenu?: (ctx: FileManagerContextMenuContext) => ReactNode;\n  renderEmpty?: (ctx: FileManagerEmptyContext) => ReactNode;\n  renderLoading?: () => ReactNode;\n  renderStatusBar?: (ctx: FileManagerStatusBarContext) => ReactNode;\n  renderDeleteConfirm?: (ctx: FileManagerDeleteConfirmContext) => ReactNode;\n\n  // Slots\n  sidebar?: ReactNode;\n  details?: ReactNode;\n\n  // Toolbar config\n  toolbar?: boolean;\n  title?: string;\n  showNewFile?: boolean;\n  showNewFolder?: boolean;\n  showRefresh?: boolean;\n  showSearch?: boolean;\n  showViewToggle?: boolean;\n  showIconSize?: boolean;\n  showSort?: boolean;\n  showBackForward?: boolean;\n  showUpButton?: boolean;\n  showPathBar?: boolean;\n\n  // Status bar\n  showStatusBar?: boolean;\n\n  // Context-menu config\n  contextMenu?: boolean;\n  contextMenuActions?: {\n    open?: boolean;\n    newFile?: boolean;\n    newFolder?: boolean;\n    cut?: boolean;\n    copy?: boolean;\n    paste?: boolean;\n    rename?: boolean;\n    delete?: boolean;\n    refresh?: boolean;\n  };\n\n  // Confirmation\n  confirmDelete?: boolean;\n\n  // DnD\n  enableInternalDrag?: boolean;\n  enableExternalDrop?: boolean;\n  enableMarqueeSelection?: boolean;\n\n  // Virtualization (list view only at v0.1.0)\n  virtualize?: FileManagerVirtualizeMode;\n  virtualizeThreshold?: number;\n\n  // Polymorphic\n  className?: string;\n  style?: CSSProperties;\n\n  // Imperative handle\n  ref?: Ref<FileManagerHandle>;\n\n  // i18n labels\n  labels?: Partial<FileManagerLabels>;\n}\n\nexport const DEFAULT_FILE_MANAGER_LABELS: FileManagerLabels = {\n  title: \"Files\",\n  back: \"Back\",\n  forward: \"Forward\",\n  up: \"Up\",\n  refresh: \"Refresh\",\n  newFile: \"New File\",\n  newFolder: \"New Folder\",\n  searchPlaceholder: \"Search...\",\n  viewGrid: \"Grid view\",\n  viewList: \"List view\",\n  iconSizeSmall: \"Small icons\",\n  iconSizeMedium: \"Medium icons\",\n  iconSizeLarge: \"Large icons\",\n  sortByName: \"Name\",\n  sortByModified: \"Date Modified\",\n  sortBySize: \"Size\",\n  sortByType: \"Type\",\n  sortAsc: \"Ascending\",\n  sortDesc: \"Descending\",\n  contextOpen: \"Open\",\n  contextNewFile: \"New File\",\n  contextNewFolder: \"New Folder\",\n  contextCut: \"Cut\",\n  contextCopy: \"Copy\",\n  contextPaste: \"Paste\",\n  contextRename: \"Rename\",\n  contextDelete: \"Delete\",\n  contextRefresh: \"Refresh\",\n  deleteConfirmTitle: \"Delete items?\",\n  deleteConfirmDescription: \"This action cannot be undone.\",\n  deleteConfirmAction: \"Delete\",\n  deleteConfirmCancel: \"Cancel\",\n  emptyTitle: \"This folder is empty\",\n  loading: \"Loading…\",\n  loadError: \"Failed to load\",\n  retry: \"Retry\",\n  externalDropOverlay: \"Drop files here\",\n  itemCount: \"items\",\n  itemCountSelected: \"selected\",\n  totalSize: \"total\",\n  pathRoot: \"Files\",\n  columnName: \"Name\",\n  columnModified: \"Modified\",\n  columnSize: \"Size\",\n  columnType: \"Type\",\n};\n",
      "type": "registry:component",
      "target": "components/file-manager/types.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/hooks/use-file-manager-context.ts",
      "content": "\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport type {\n  FileManagerActions,\n  FileManagerItem,\n  FileManagerLabels,\n  FileManagerState,\n} from \"../types\";\n\nexport interface FileManagerContextValue {\n  state: FileManagerState;\n  actions: FileManagerActions;\n  /** Visible items in the current folder, post-sort + filter + hidden. */\n  items: FileManagerItem[];\n  /** Total node count across the entire `nodes` tree (recursive). */\n  totalCount: number;\n  /** Visible-item count in the current folder. */\n  visibleCount: number;\n  /** Sum of `size` over visible items (folders contribute 0). */\n  totalSize: number;\n  /** Header config flags (read by toolbar atoms). */\n  showNewFile: boolean;\n  showNewFolder: boolean;\n  showRefresh: boolean;\n  showSearch: boolean;\n  showViewToggle: boolean;\n  showIconSize: boolean;\n  showSort: boolean;\n  showBackForward: boolean;\n  showUpButton: boolean;\n  showPathBar: boolean;\n  showStatusBar: boolean;\n  title?: string;\n  labels: FileManagerLabels;\n}\n\nexport const FileManagerContext =\n  createContext<FileManagerContextValue | null>(null);\n\n/**\n * Public consumer hook for advanced custom-chrome composition. Used by the\n * standalone toolbar atoms internally and exposed to consumers writing a\n * `renderToolbar` slot.\n *\n * Throws if called outside `<FileManager>`.\n */\nexport function useFileManager(): FileManagerContextValue {\n  const ctx = useContext(FileManagerContext);\n  if (!ctx) {\n    throw new Error(\n      \"useFileManager() must be called inside <FileManager>\",\n    );\n  }\n  return ctx;\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/hooks/use-file-manager-context.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/hooks/use-current-folder.ts",
      "content": "\"use client\";\n\nimport { useCallback, useMemo, useState } from \"react\";\nimport type {\n  FileManagerCurrentFolderChangeArgs,\n  FsNode,\n} from \"../types\";\nimport { buildPath } from \"../lib/path\";\nimport { useNavigationHistory } from \"./use-navigation-history\";\n\ninterface UseCurrentFolderArgs {\n  nodes: FsNode[];\n  index: Map<string, FsNode>;\n  controlledFolderId?: string | null;\n  defaultFolderId?: string | null;\n  onCurrentFolderChange?: (\n    args: FileManagerCurrentFolderChangeArgs,\n  ) => void;\n  enableHistory: boolean;\n  controlledBack?: string[];\n  controlledForward?: string[];\n}\n\nexport interface UseCurrentFolderResult {\n  currentFolderId: string | null;\n  path: FsNode[];\n  navigateTo: (folderId: string | null) => void;\n  navigateUp: () => void;\n  navigateBack: () => void;\n  navigateForward: () => void;\n  historyBackIds: string[];\n  historyForwardIds: string[];\n}\n\nexport function useCurrentFolder(\n  args: UseCurrentFolderArgs,\n): UseCurrentFolderResult {\n  const {\n    index,\n    controlledFolderId,\n    defaultFolderId,\n    onCurrentFolderChange,\n    enableHistory,\n    controlledBack,\n    controlledForward,\n  } = args;\n\n  const [internalFolderId, setInternalFolderId] = useState<string | null>(\n    defaultFolderId ?? null,\n  );\n  const currentFolderId =\n    controlledFolderId !== undefined ? controlledFolderId : internalFolderId;\n\n  const history = useNavigationHistory({\n    enableHistory,\n    controlledBack,\n    controlledForward,\n  });\n\n  const path = useMemo(\n    () => buildPath(currentFolderId, index),\n    [currentFolderId, index],\n  );\n\n  const setFolder = useCallback(\n    (next: string | null, pushToHistory: boolean) => {\n      if (next === currentFolderId) return;\n      if (pushToHistory) {\n        history.pushVisit(currentFolderId);\n      }\n      if (controlledFolderId === undefined) setInternalFolderId(next);\n      onCurrentFolderChange?.({ folderId: next });\n    },\n    [currentFolderId, controlledFolderId, onCurrentFolderChange, history],\n  );\n\n  const navigateTo = useCallback(\n    (folderId: string | null) => {\n      setFolder(folderId, true);\n    },\n    [setFolder],\n  );\n\n  const navigateUp = useCallback(() => {\n    if (currentFolderId === null) return;\n    const current = index.get(currentFolderId);\n    const parentId = current?.parentId ?? null;\n    setFolder(parentId, true);\n  }, [currentFolderId, index, setFolder]);\n\n  const navigateBack = useCallback(() => {\n    const previous = history.popBack(currentFolderId);\n    if (previous === undefined) return;\n    if (controlledFolderId === undefined) setInternalFolderId(previous);\n    onCurrentFolderChange?.({ folderId: previous });\n  }, [history, currentFolderId, controlledFolderId, onCurrentFolderChange]);\n\n  const navigateForward = useCallback(() => {\n    const next = history.popForward(currentFolderId);\n    if (next === undefined) return;\n    if (controlledFolderId === undefined) setInternalFolderId(next);\n    onCurrentFolderChange?.({ folderId: next });\n  }, [history, currentFolderId, controlledFolderId, onCurrentFolderChange]);\n\n  return {\n    currentFolderId,\n    path,\n    navigateTo,\n    navigateUp,\n    navigateBack,\n    navigateForward,\n    historyBackIds: history.backIds,\n    historyForwardIds: history.forwardIds,\n  };\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/hooks/use-current-folder.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/hooks/use-navigation-history.ts",
      "content": "\"use client\";\n\nimport { useCallback, useState } from \"react\";\n\nconst MAX_HISTORY = 50;\n\ninterface UseNavigationHistoryArgs {\n  enableHistory: boolean;\n  controlledBack?: string[];\n  controlledForward?: string[];\n}\n\nexport interface UseNavigationHistoryResult {\n  backIds: string[];\n  forwardIds: string[];\n  /** Push the *previous* folder onto the back stack when a fresh navigation happens. */\n  pushVisit: (previousFolderId: string | null) => void;\n  /** Pop the back stack; returns the destination id (or `undefined` if empty). */\n  popBack: (currentFolderId: string | null) => string | null | undefined;\n  /** Pop the forward stack; returns the destination id (or `undefined` if empty). */\n  popForward: (currentFolderId: string | null) => string | null | undefined;\n}\n\nconst ROOT_SENTINEL = \"__root__\";\nconst encode = (id: string | null): string =>\n  id === null ? ROOT_SENTINEL : id;\nconst decode = (s: string | undefined): string | null | undefined =>\n  s === undefined ? undefined : s === ROOT_SENTINEL ? null : s;\n\nexport function useNavigationHistory(\n  args: UseNavigationHistoryArgs,\n): UseNavigationHistoryResult {\n  const { enableHistory, controlledBack, controlledForward } = args;\n\n  const [internalBack, setInternalBack] = useState<string[]>([]);\n  const [internalForward, setInternalForward] = useState<string[]>([]);\n\n  const backIds =\n    controlledBack ?? (enableHistory ? internalBack : []);\n  const forwardIds =\n    controlledForward ?? (enableHistory ? internalForward : []);\n\n  const pushVisit = useCallback(\n    (previousFolderId: string | null) => {\n      if (!enableHistory) return;\n      if (controlledBack) return;\n      setInternalBack((prev) => {\n        const next = [...prev, encode(previousFolderId)];\n        if (next.length > MAX_HISTORY)\n          next.splice(0, next.length - MAX_HISTORY);\n        return next;\n      });\n      setInternalForward([]);\n    },\n    [enableHistory, controlledBack],\n  );\n\n  const popBack = useCallback(\n    (currentFolderId: string | null): string | null | undefined => {\n      if (!enableHistory) return undefined;\n      if (controlledBack) return undefined;\n      if (internalBack.length === 0) return undefined;\n      const previous = decode(internalBack[internalBack.length - 1]);\n      setInternalBack(internalBack.slice(0, -1));\n      setInternalForward([...internalForward, encode(currentFolderId)]);\n      return previous;\n    },\n    [enableHistory, controlledBack, internalBack, internalForward],\n  );\n\n  const popForward = useCallback(\n    (currentFolderId: string | null): string | null | undefined => {\n      if (!enableHistory) return undefined;\n      if (controlledForward) return undefined;\n      if (internalForward.length === 0) return undefined;\n      const next = decode(internalForward[internalForward.length - 1]);\n      setInternalForward(internalForward.slice(0, -1));\n      setInternalBack([...internalBack, encode(currentFolderId)]);\n      return next;\n    },\n    [enableHistory, controlledForward, internalBack, internalForward],\n  );\n\n  return { backIds, forwardIds, pushVisit, popBack, popForward };\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/hooks/use-navigation-history.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/hooks/use-selection.ts",
      "content": "\"use client\";\n\nimport { useCallback, useMemo, useRef, useState } from \"react\";\nimport type {\n  FileManagerSelectionChangeArgs,\n  FsNode,\n} from \"../types\";\n\ninterface UseSelectionArgs {\n  controlledIds?: Set<string>;\n  defaultIds?: Set<string>;\n  onSelectedChange?: (args: FileManagerSelectionChangeArgs) => void;\n  /** Used to drop ids that no longer reference live nodes. */\n  index: Map<string, FsNode>;\n}\n\nexport interface UseSelectionResult {\n  selectedIds: ReadonlySet<string>;\n  select: (idOrIds: string | string[]) => void;\n  toggleSelection: (id: string) => void;\n  clearSelection: () => void;\n  selectAll: (visibleIds: string[]) => void;\n  /** For Shift+click range select. Read in event handler context only. */\n  getSelectionAnchorId: () => string | null;\n  setSelectionAnchorId: (id: string | null) => void;\n}\n\nexport function useSelection(args: UseSelectionArgs): UseSelectionResult {\n  const { controlledIds, defaultIds, onSelectedChange, index } = args;\n\n  const [internalIds, setInternalIds] = useState<Set<string>>(\n    () => defaultIds ?? new Set(),\n  );\n  const anchorRef = useRef<string | null>(null);\n\n  const selectedIds = controlledIds ?? internalIds;\n\n  const setIds = useCallback(\n    (next: Set<string>) => {\n      if (controlledIds === undefined) setInternalIds(next);\n      onSelectedChange?.({ ids: next });\n    },\n    [controlledIds, onSelectedChange],\n  );\n\n  const select = useCallback(\n    (idOrIds: string | string[]) => {\n      const ids = Array.isArray(idOrIds) ? idOrIds : [idOrIds];\n      setIds(new Set(ids));\n      if (ids.length > 0) anchorRef.current = ids[0];\n    },\n    [setIds],\n  );\n\n  const toggleSelection = useCallback(\n    (id: string) => {\n      const next = new Set(selectedIds);\n      if (next.has(id)) next.delete(id);\n      else next.add(id);\n      setIds(next);\n      anchorRef.current = id;\n    },\n    [selectedIds, setIds],\n  );\n\n  const clearSelection = useCallback(() => {\n    setIds(new Set());\n    anchorRef.current = null;\n  }, [setIds]);\n\n  const selectAll = useCallback(\n    (visibleIds: string[]) => {\n      setIds(new Set(visibleIds));\n    },\n    [setIds],\n  );\n\n  const getSelectionAnchorId = useCallback(() => anchorRef.current, []);\n  const setSelectionAnchorId = useCallback((id: string | null) => {\n    anchorRef.current = id;\n  }, []);\n\n  // Sanitize: drop ids that no longer reference real nodes\n  const sanitized = useMemo(() => {\n    if (selectedIds.size === 0) return selectedIds;\n    let dirty = false;\n    const next = new Set<string>();\n    for (const id of selectedIds) {\n      if (index.has(id)) next.add(id);\n      else dirty = true;\n    }\n    return dirty ? next : selectedIds;\n  }, [selectedIds, index]);\n\n  return {\n    selectedIds: sanitized,\n    select,\n    toggleSelection,\n    clearSelection,\n    selectAll,\n    getSelectionAnchorId,\n    setSelectionAnchorId,\n  };\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/hooks/use-selection.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/hooks/use-clipboard.ts",
      "content": "\"use client\";\n\nimport { useCallback, useState } from \"react\";\nimport {\n  EMPTY_CLIPBOARD,\n  useFileClipboard,\n  type FileClipboard,\n} from \"../../_shared/file-clipboard\";\nimport type { FileManagerClipboardChangeArgs } from \"../types\";\n\ninterface UseClipboardArgs {\n  /** Controlled clipboard (consumer manages state). */\n  controlledClipboard?: FileClipboard;\n  /** Initial value for uncontrolled mode. */\n  defaultClipboard?: FileClipboard;\n  onClipboardChange?: (args: FileManagerClipboardChangeArgs) => void;\n}\n\nexport interface UseClipboardResult {\n  clipboard: FileClipboard;\n  cut: (ids: string[]) => void;\n  copy: (ids: string[]) => void;\n  clearClipboard: () => void;\n  setClipboard: (next: FileClipboard) => void;\n}\n\n/**\n * Resolves clipboard state across three modes:\n *  1. Controlled (`controlledClipboard` prop given) — consumer drives.\n *  2. Provider (`<FileClipboardProvider>` up the tree) — synced across\n *     instances inside the provider.\n *  3. Internal — local `useState`.\n *\n * Mode is decided in priority order: controlled > provider > internal.\n */\nexport function useClipboard(args: UseClipboardArgs): UseClipboardResult {\n  const { controlledClipboard, defaultClipboard, onClipboardChange } = args;\n\n  const provider = useFileClipboard();\n  const hasProvider = provider.__hasProvider;\n\n  const [internalClipboard, setInternalClipboard] = useState<FileClipboard>(\n    () => defaultClipboard ?? EMPTY_CLIPBOARD,\n  );\n\n  // Resolve current clipboard\n  let clipboard: FileClipboard;\n  if (controlledClipboard) {\n    clipboard = controlledClipboard;\n  } else if (hasProvider) {\n    clipboard = provider.clipboard;\n  } else {\n    clipboard = internalClipboard;\n  }\n\n  const setClipboard = useCallback(\n    (next: FileClipboard) => {\n      if (!controlledClipboard) {\n        if (hasProvider) provider.setClipboard(next);\n        else setInternalClipboard(next);\n      }\n      onClipboardChange?.({ clipboard: next });\n    },\n    [controlledClipboard, hasProvider, provider, onClipboardChange],\n  );\n\n  const cut = useCallback(\n    (ids: string[]) => {\n      setClipboard({ kind: \"cut\", ids });\n    },\n    [setClipboard],\n  );\n\n  const copy = useCallback(\n    (ids: string[]) => {\n      setClipboard({ kind: \"copy\", ids });\n    },\n    [setClipboard],\n  );\n\n  const clearClipboard = useCallback(() => {\n    setClipboard(EMPTY_CLIPBOARD);\n  }, [setClipboard]);\n\n  return {\n    clipboard,\n    cut,\n    copy,\n    clearClipboard,\n    setClipboard,\n  };\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/hooks/use-clipboard.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/hooks/use-sort-search.ts",
      "content": "\"use client\";\n\nimport { useCallback, useState } from \"react\";\nimport type {\n  FileManagerIconSize,\n  FileManagerIconSizeChangeArgs,\n  FileManagerSearchChangeArgs,\n  FileManagerSortChangeArgs,\n  FileManagerSortState,\n  FileManagerViewMode,\n  FileManagerViewModeChangeArgs,\n} from \"../types\";\n\ninterface UseSortSearchArgs {\n  controlledSort?: FileManagerSortState;\n  defaultSort?: FileManagerSortState;\n  onSortChange?: (args: FileManagerSortChangeArgs) => void;\n\n  controlledSearch?: string;\n  defaultSearch?: string;\n  onSearchChange?: (args: FileManagerSearchChangeArgs) => void;\n\n  controlledViewMode?: FileManagerViewMode;\n  defaultViewMode?: FileManagerViewMode;\n  onViewModeChange?: (args: FileManagerViewModeChangeArgs) => void;\n\n  controlledIconSize?: FileManagerIconSize;\n  defaultIconSize?: FileManagerIconSize;\n  onIconSizeChange?: (args: FileManagerIconSizeChangeArgs) => void;\n}\n\nconst DEFAULT_SORT: FileManagerSortState = { key: \"name\", order: \"asc\" };\n\nexport interface UseSortSearchResult {\n  sort: FileManagerSortState;\n  searchQuery: string;\n  viewMode: FileManagerViewMode;\n  iconSize: FileManagerIconSize;\n  setSort: (next: FileManagerSortState) => void;\n  setSearchQuery: (next: string) => void;\n  setViewMode: (next: FileManagerViewMode) => void;\n  setIconSize: (next: FileManagerIconSize) => void;\n}\n\nexport function useSortSearch(args: UseSortSearchArgs): UseSortSearchResult {\n  const {\n    controlledSort,\n    defaultSort,\n    onSortChange,\n    controlledSearch,\n    defaultSearch,\n    onSearchChange,\n    controlledViewMode,\n    defaultViewMode,\n    onViewModeChange,\n    controlledIconSize,\n    defaultIconSize,\n    onIconSizeChange,\n  } = args;\n\n  const [internalSort, setInternalSort] = useState<FileManagerSortState>(\n    defaultSort ?? DEFAULT_SORT,\n  );\n  const [internalSearch, setInternalSearch] = useState<string>(\n    defaultSearch ?? \"\",\n  );\n  const [internalViewMode, setInternalViewMode] = useState<FileManagerViewMode>(\n    defaultViewMode ?? \"grid\",\n  );\n  const [internalIconSize, setInternalIconSize] = useState<FileManagerIconSize>(\n    defaultIconSize ?? \"md\",\n  );\n\n  const sort = controlledSort ?? internalSort;\n  const searchQuery = controlledSearch ?? internalSearch;\n  const viewMode = controlledViewMode ?? internalViewMode;\n  const iconSize = controlledIconSize ?? internalIconSize;\n\n  const setSort = useCallback(\n    (next: FileManagerSortState) => {\n      if (controlledSort === undefined) setInternalSort(next);\n      onSortChange?.({ sort: next });\n    },\n    [controlledSort, onSortChange],\n  );\n\n  const setSearchQuery = useCallback(\n    (next: string) => {\n      if (controlledSearch === undefined) setInternalSearch(next);\n      onSearchChange?.({ query: next });\n    },\n    [controlledSearch, onSearchChange],\n  );\n\n  const setViewMode = useCallback(\n    (next: FileManagerViewMode) => {\n      if (controlledViewMode === undefined) setInternalViewMode(next);\n      onViewModeChange?.({ mode: next });\n    },\n    [controlledViewMode, onViewModeChange],\n  );\n\n  const setIconSize = useCallback(\n    (next: FileManagerIconSize) => {\n      if (controlledIconSize === undefined) setInternalIconSize(next);\n      onIconSizeChange?.({ size: next });\n    },\n    [controlledIconSize, onIconSizeChange],\n  );\n\n  return {\n    sort,\n    searchQuery,\n    viewMode,\n    iconSize,\n    setSort,\n    setSearchQuery,\n    setViewMode,\n    setIconSize,\n  };\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/hooks/use-sort-search.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/hooks/use-visible-items.ts",
      "content": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport type {\n  FileClipboard,\n  FileManagerItem,\n  FileManagerSortState,\n  FsNode,\n} from \"../types\";\nimport { defaultSort, getChildrenOf } from \"../lib/tree-utils\";\nimport { getNodeExtension } from \"../lib/icons\";\n\ninterface UseVisibleItemsArgs {\n  nodes: FsNode[];\n  index: Map<string, FsNode>;\n  currentFolderId: string | null;\n  selectedIds: ReadonlySet<string>;\n  focusedId: string | null;\n  clipboard: FileClipboard;\n  sort: FileManagerSortState;\n  searchQuery: string;\n  showHidden: boolean;\n  isHidden?: (node: FsNode) => boolean;\n  filterItems?: (node: FsNode, query: string) => boolean;\n  sortItems?: (a: FsNode, b: FsNode, sort: FileManagerSortState) => number;\n}\n\nconst defaultIsHidden = (node: FsNode) => node.name.startsWith(\".\");\n\nconst defaultFilter = (node: FsNode, query: string) =>\n  node.name.toLowerCase().includes(query.toLowerCase());\n\nfunction defaultSortBy(\n  a: FsNode,\n  b: FsNode,\n  sort: FileManagerSortState,\n): number {\n  // folders always before files regardless of sort key (Mac Finder behavior)\n  if (a.type !== b.type) return a.type === \"folder\" ? -1 : 1;\n\n  let cmp = 0;\n  switch (sort.key) {\n    case \"name\":\n      cmp = a.name.localeCompare(b.name, undefined, { sensitivity: \"base\" });\n      break;\n    case \"modified\": {\n      const ta = a.modifiedAt ? new Date(a.modifiedAt).getTime() : 0;\n      const tb = b.modifiedAt ? new Date(b.modifiedAt).getTime() : 0;\n      cmp = ta - tb;\n      break;\n    }\n    case \"size\":\n      cmp = (a.size ?? 0) - (b.size ?? 0);\n      break;\n    case \"type\": {\n      const ea = getNodeExtension(a);\n      const eb = getNodeExtension(b);\n      cmp = ea.localeCompare(eb, undefined, { sensitivity: \"base\" });\n      if (cmp === 0) {\n        cmp = a.name.localeCompare(b.name, undefined, { sensitivity: \"base\" });\n      }\n      break;\n    }\n  }\n  return sort.order === \"asc\" ? cmp : -cmp;\n}\n\nexport interface UseVisibleItemsResult {\n  items: FileManagerItem[];\n  /** Total size in bytes across visible items (folders contribute 0). */\n  totalSize: number;\n  /** Visible item count (post-filter, post-hidden). */\n  visibleCount: number;\n}\n\nexport function useVisibleItems(\n  args: UseVisibleItemsArgs,\n): UseVisibleItemsResult {\n  const {\n    nodes,\n    index,\n    currentFolderId,\n    selectedIds,\n    focusedId,\n    clipboard,\n    sort,\n    searchQuery,\n    showHidden,\n    isHidden,\n    filterItems,\n    sortItems,\n  } = args;\n\n  return useMemo(() => {\n    const children = getChildrenOf(currentFolderId, nodes, index) ?? [];\n    const hiddenFn = isHidden ?? defaultIsHidden;\n    const filterFn = filterItems ?? defaultFilter;\n    const sorter = sortItems\n      ? (a: FsNode, b: FsNode) => sortItems(a, b, sort)\n      : (a: FsNode, b: FsNode) => defaultSortBy(a, b, sort);\n\n    let filtered = showHidden\n      ? [...children]\n      : children.filter((n) => !hiddenFn(n));\n    if (searchQuery.trim()) {\n      filtered = filtered.filter((n) => filterFn(n, searchQuery));\n    }\n    filtered.sort(sorter);\n\n    const cutSet = new Set(\n      clipboard.kind === \"cut\" ? clipboard.ids : [],\n    );\n\n    const total = filtered.length;\n    const totalSize = filtered.reduce(\n      (sum, node) => sum + (node.type === \"file\" ? (node.size ?? 0) : 0),\n      0,\n    );\n    const items: FileManagerItem[] = filtered.map((node, i) => ({\n      node,\n      selected: selectedIds.has(node.id),\n      focused: focusedId === node.id,\n      cut: cutSet.has(node.id),\n      index: i,\n      totalVisible: total,\n    }));\n\n    return { items, totalSize, visibleCount: total };\n  }, [\n    nodes,\n    index,\n    currentFolderId,\n    selectedIds,\n    focusedId,\n    clipboard,\n    sort,\n    searchQuery,\n    showHidden,\n    isHidden,\n    filterItems,\n    sortItems,\n  ]);\n}\n\n// fallback so the unused-import lint doesn't fire when defaultSort is\n// referenced by tree-utils consumers expecting it to be available\nvoid defaultSort;\n",
      "type": "registry:component",
      "target": "components/file-manager/hooks/use-visible-items.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/hooks/use-lazy-load.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { FileManagerLoadChildrenArgs, FsNode } from \"../types\";\n\ninterface UseLazyLoadArgs {\n  nodes: FsNode[];\n  currentFolderId: string | null;\n  index: Map<string, FsNode>;\n  onLoadChildren?: (\n    args: FileManagerLoadChildrenArgs,\n  ) => Promise<FsNode[]>;\n}\n\nexport interface UseLazyLoadResult {\n  loadingChildren: boolean;\n  loadError: string | null;\n  retry: () => void;\n}\n\n/**\n * Auto-fires `onLoadChildren` when the current folder's `children` is\n * `undefined`. Per-folder loading + error state is keyed by folder id so\n * navigating between folders surfaces the right state without a clear effect.\n */\nexport function useLazyLoad(args: UseLazyLoadArgs): UseLazyLoadResult {\n  const { currentFolderId, index, onLoadChildren } = args;\n  const [loadingIds, setLoadingIds] = useState<Set<string>>(\n    () => new Set(),\n  );\n  const [errorById, setErrorById] = useState<Map<string, string>>(\n    () => new Map(),\n  );\n  const inFlightRef = useRef<Set<string>>(new Set());\n\n  const triggerLoad = useCallback(\n    async (folderId: string) => {\n      if (!onLoadChildren) return;\n      if (inFlightRef.current.has(folderId)) return;\n      const node = index.get(folderId);\n      if (!node || node.type !== \"folder\") return;\n      inFlightRef.current.add(folderId);\n      setLoadingIds((prev) => {\n        const next = new Set(prev);\n        next.add(folderId);\n        return next;\n      });\n      setErrorById((prev) => {\n        if (!prev.has(folderId)) return prev;\n        const next = new Map(prev);\n        next.delete(folderId);\n        return next;\n      });\n      try {\n        await onLoadChildren({ nodeId: folderId, node });\n      } catch (err) {\n        const msg = err instanceof Error ? err.message : \"Failed to load\";\n        setErrorById((prev) => {\n          const next = new Map(prev);\n          next.set(folderId, msg);\n          return next;\n        });\n      } finally {\n        inFlightRef.current.delete(folderId);\n        setLoadingIds((prev) => {\n          if (!prev.has(folderId)) return prev;\n          const next = new Set(prev);\n          next.delete(folderId);\n          return next;\n        });\n      }\n    },\n    [onLoadChildren, index],\n  );\n\n  useEffect(() => {\n    if (!onLoadChildren || currentFolderId === null) return;\n    const node = index.get(currentFolderId);\n    if (!node || node.type !== \"folder\") return;\n    if (node.children !== undefined) return;\n    if (errorById.has(currentFolderId)) return;\n    // Defer to the next microtask so the synchronous setLoadingIds inside\n    // triggerLoad doesn't violate react-hooks/set-state-in-effect (kickoff\n    // for async data fetch — sanctioned pattern).\n    queueMicrotask(() => {\n      void triggerLoad(currentFolderId);\n    });\n  }, [currentFolderId, index, onLoadChildren, triggerLoad, errorById]);\n\n  const retry = useCallback(() => {\n    if (currentFolderId === null) return;\n    setErrorById((prev) => {\n      if (!prev.has(currentFolderId)) return prev;\n      const next = new Map(prev);\n      next.delete(currentFolderId);\n      return next;\n    });\n    void triggerLoad(currentFolderId);\n  }, [currentFolderId, triggerLoad]);\n\n  const loadingChildren =\n    currentFolderId !== null && loadingIds.has(currentFolderId);\n  const loadError =\n    currentFolderId !== null\n      ? (errorById.get(currentFolderId) ?? null)\n      : null;\n\n  return { loadingChildren, loadError, retry };\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/hooks/use-lazy-load.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/hooks/use-marquee.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { PointerEvent as ReactPointerEvent } from \"react\";\nimport { getItemRects, rectanglesIntersect, type Rect } from \"../lib/intersect\";\n\ninterface UseMarqueeArgs {\n  enabled: boolean;\n  /** Apply the marquee's current selection. `additive` = shift held (add to existing). */\n  applySelection: (ids: string[], additive: boolean) => void;\n  /** Snapshot the current selection at marquee-start so additive mode can restore + extend. */\n  snapshotSelection: () => Set<string>;\n  /** Container element used for both rect query + scrolling. */\n  containerRef: React.RefObject<HTMLDivElement | null>;\n}\n\nexport interface MarqueeRect {\n  left: number;\n  top: number;\n  width: number;\n  height: number;\n}\n\nexport interface UseMarqueeResult {\n  rect: MarqueeRect | null;\n  active: boolean;\n  /** Bind to the content pane's pointer-down — only fires when pointer-down lands on whitespace. */\n  onPointerDown: (e: ReactPointerEvent<HTMLDivElement>) => void;\n}\n\nexport function useMarquee(args: UseMarqueeArgs): UseMarqueeResult {\n  const { enabled, applySelection, snapshotSelection, containerRef } = args;\n\n  const [rect, setRect] = useState<MarqueeRect | null>(null);\n  const [active, setActive] = useState(false);\n  const startRef = useRef<{\n    x: number;\n    y: number;\n    additive: boolean;\n    snapshot: Set<string>;\n  } | null>(null);\n\n  // Stable refs to handlers so cleanup matches the registered listener.\n  const moveHandlerRef = useRef<((e: PointerEvent) => void) | null>(null);\n  const upHandlerRef = useRef<((e: PointerEvent) => void) | null>(null);\n\n  // v0.1.1 (review): unmount cleanup — the window listeners were only\n  // removed in handleUp, so unmounting mid-drag leaked all three listeners\n  // (and the leaked move handler kept calling setState on the unmounted\n  // component).\n  useEffect(() => {\n    return () => {\n      if (moveHandlerRef.current) {\n        window.removeEventListener(\"pointermove\", moveHandlerRef.current);\n      }\n      if (upHandlerRef.current) {\n        window.removeEventListener(\"pointerup\", upHandlerRef.current);\n        window.removeEventListener(\"pointercancel\", upHandlerRef.current);\n      }\n      moveHandlerRef.current = null;\n      upHandlerRef.current = null;\n      startRef.current = null;\n    };\n  }, []);\n\n  const onPointerDown = useCallback(\n    (e: ReactPointerEvent<HTMLDivElement>) => {\n      if (!enabled) return;\n      if (e.button !== 0) return;\n      const additive = e.shiftKey;\n      const initialSelection = additive\n        ? snapshotSelection()\n        : new Set<string>();\n      startRef.current = {\n        x: e.clientX,\n        y: e.clientY,\n        additive,\n        snapshot: initialSelection,\n      };\n      setActive(true);\n\n      const handleMove = (moveEvent: PointerEvent) => {\n        const start = startRef.current;\n        const container = containerRef.current;\n        if (!start || !container) return;\n        const containerRect = container.getBoundingClientRect();\n        const left = Math.min(start.x, moveEvent.clientX);\n        const top = Math.min(start.y, moveEvent.clientY);\n        const right = Math.max(start.x, moveEvent.clientX);\n        const bottom = Math.max(start.y, moveEvent.clientY);\n        setRect({\n          left: left - containerRect.left,\n          top: top - containerRect.top + container.scrollTop,\n          width: right - left,\n          height: bottom - top,\n        });\n        const marqueeRect: Rect = { left, top, right, bottom };\n        const itemRects = getItemRects(container);\n        const hit = new Set<string>();\n        for (const [id, ir] of itemRects) {\n          if (rectanglesIntersect(marqueeRect, ir)) hit.add(id);\n        }\n        const ids = Array.from(hit);\n        applySelection(\n          start.additive\n            ? Array.from(new Set([...start.snapshot, ...ids]))\n            : ids,\n          start.additive,\n        );\n      };\n\n      const handleUp = () => {\n        startRef.current = null;\n        setActive(false);\n        setRect(null);\n        if (moveHandlerRef.current)\n          window.removeEventListener(\"pointermove\", moveHandlerRef.current);\n        if (upHandlerRef.current) {\n          window.removeEventListener(\"pointerup\", upHandlerRef.current);\n          window.removeEventListener(\"pointercancel\", upHandlerRef.current);\n        }\n        moveHandlerRef.current = null;\n        upHandlerRef.current = null;\n      };\n\n      moveHandlerRef.current = handleMove;\n      upHandlerRef.current = handleUp;\n      window.addEventListener(\"pointermove\", handleMove);\n      window.addEventListener(\"pointerup\", handleUp);\n      window.addEventListener(\"pointercancel\", handleUp);\n    },\n    [enabled, snapshotSelection, applySelection, containerRef],\n  );\n\n  return { rect, active, onPointerDown };\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/hooks/use-marquee.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/hooks/use-drag.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { DragEvent } from \"react\";\nimport type {\n  FileManagerDropPosition,\n  FileManagerExternalDropArgs,\n  FileManagerItem,\n  FileManagerMoveArgs,\n  FsNode,\n} from \"../types\";\nimport { isLegalDrop } from \"../lib/validation\";\nimport { indexNodes } from \"../lib/tree-utils\";\n\n/** Same MIME marker file-tree uses — enables cross-component drops. */\nconst INTERNAL_MIME = \"application/x-ilinxa-file-tree\";\n\nconst AUTO_SCROLL_EDGE_PX = 24;\nconst AUTO_SCROLL_PX_PER_FRAME = 4;\n\nexport interface DragOverState {\n  /** Item id currently being hovered as a drop target. `null` = container. */\n  itemId: string | null;\n  valid: boolean;\n}\n\ninterface UseDragArgs {\n  nodes: FsNode[];\n  currentFolderId: string | null;\n  enableInternalDrag: boolean;\n  enableExternalDrop: boolean;\n  selectedIds: ReadonlySet<string>;\n  onMove?: (args: FileManagerMoveArgs) => void;\n  onExternalDrop?: (args: FileManagerExternalDropArgs) => void;\n  /** Replace selection with a single id (used at drag-start when source isn't selected). */\n  selectIds: (idOrIds: string | string[]) => void;\n}\n\nexport interface UseDragResult {\n  dragOver: DragOverState | null;\n  isExternalDragging: boolean;\n  isInternalDragging: boolean;\n  // Item-level handlers\n  onItemDragStart: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragOver: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragLeave: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDrop: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragEnd: (e: DragEvent, item: FileManagerItem) => void;\n  // Container handlers (scroll area)\n  onContainerDragEnter: (e: DragEvent) => void;\n  onContainerDragOver: (e: DragEvent) => void;\n  onContainerDragLeave: (e: DragEvent) => void;\n  onContainerDrop: (e: DragEvent) => void;\n}\n\nexport function useDrag(args: UseDragArgs): UseDragResult {\n  const {\n    nodes,\n    currentFolderId,\n    enableInternalDrag,\n    enableExternalDrop,\n    selectedIds,\n    onMove,\n    onExternalDrop,\n    selectIds,\n  } = args;\n\n  const [dragOver, setDragOver] = useState<DragOverState | null>(null);\n  const [isExternalDragging, setIsExternalDragging] = useState(false);\n  const [isInternalDragging, setIsInternalDragging] = useState(false);\n  const isInternalDraggingRef = useRef(false);\n  const externalEnterCountRef = useRef(0);\n  const dragSourceIdsRef = useRef<string[]>([]);\n  const autoScrollRef = useRef<{\n    container: HTMLElement | null;\n    direction: number;\n    raf: number | null;\n  }>({ container: null, direction: 0, raf: null });\n\n  const nodeIndex = useMemo(() => indexNodes(nodes), [nodes]);\n\n  const stopAutoScroll = useCallback(() => {\n    const ref = autoScrollRef.current;\n    if (ref.raf !== null) {\n      cancelAnimationFrame(ref.raf);\n      ref.raf = null;\n    }\n    ref.direction = 0;\n    ref.container = null;\n  }, []);\n\n  const startAutoScroll = useCallback(\n    (container: HTMLElement | null, clientY: number) => {\n      if (!container) return;\n      const rect = container.getBoundingClientRect();\n      let dir = 0;\n      if (clientY - rect.top < AUTO_SCROLL_EDGE_PX) dir = -1;\n      else if (rect.bottom - clientY < AUTO_SCROLL_EDGE_PX) dir = 1;\n      const ref = autoScrollRef.current;\n      ref.container = container;\n      if (dir !== ref.direction) {\n        ref.direction = dir;\n        if (ref.raf !== null) {\n          cancelAnimationFrame(ref.raf);\n          ref.raf = null;\n        }\n        if (dir !== 0) {\n          const tick = () => {\n            const r = autoScrollRef.current;\n            if (r.container && r.direction !== 0) {\n              r.container.scrollTop +=\n                r.direction * AUTO_SCROLL_PX_PER_FRAME;\n              r.raf = requestAnimationFrame(tick);\n            } else {\n              r.raf = null;\n            }\n          };\n          ref.raf = requestAnimationFrame(tick);\n        }\n      }\n    },\n    [],\n  );\n\n  useEffect(() => {\n    return () => stopAutoScroll();\n  }, [stopAutoScroll]);\n\n  const onItemDragStart = useCallback(\n    (e: DragEvent, item: FileManagerItem) => {\n      if (!enableInternalDrag) return;\n      let ids: string[];\n      if (selectedIds.has(item.node.id) && selectedIds.size > 1) {\n        ids = Array.from(selectedIds);\n      } else {\n        ids = [item.node.id];\n        selectIds(item.node.id);\n      }\n      dragSourceIdsRef.current = ids;\n      isInternalDraggingRef.current = true;\n      setIsInternalDragging(true);\n      try {\n        e.dataTransfer.effectAllowed = \"move\";\n        e.dataTransfer.setData(INTERNAL_MIME, \"1\");\n      } catch {\n        /* ignored */\n      }\n    },\n    [enableInternalDrag, selectedIds, selectIds],\n  );\n\n  const onItemDragEnd = useCallback(() => {\n    isInternalDraggingRef.current = false;\n    setIsInternalDragging(false);\n    dragSourceIdsRef.current = [];\n    setDragOver(null);\n    stopAutoScroll();\n  }, [stopAutoScroll]);\n\n  const onItemDragOver = useCallback(\n    (e: DragEvent, item: FileManagerItem) => {\n      if (!isInternalDraggingRef.current) return;\n      e.preventDefault();\n      // Drops are only legal on folders\n      const validTarget = item.node.type === \"folder\";\n      const position: FileManagerDropPosition = \"inside\";\n      const valid =\n        validTarget &&\n        isLegalDrop({\n          srcIds: dragSourceIdsRef.current,\n          targetId: item.node.id,\n          position,\n          index: nodeIndex,\n        });\n      e.dataTransfer.dropEffect = valid ? \"move\" : \"none\";\n      setDragOver({ itemId: item.node.id, valid });\n      const scroller = (e.currentTarget as HTMLElement).closest(\n        \"[data-file-manager-scroller]\",\n      ) as HTMLElement | null;\n      startAutoScroll(scroller, e.clientY);\n    },\n    [nodeIndex, startAutoScroll],\n  );\n\n  const onItemDragLeave = useCallback(\n    (_e: DragEvent, item: FileManagerItem) => {\n      setDragOver((prev) =>\n        prev?.itemId === item.node.id ? null : prev,\n      );\n    },\n    [],\n  );\n\n  const onItemDrop = useCallback(\n    (e: DragEvent, item: FileManagerItem) => {\n      if (!isInternalDraggingRef.current) return;\n      e.preventDefault();\n      const validTarget = item.node.type === \"folder\";\n      const ids = dragSourceIdsRef.current;\n      if (!validTarget) {\n        // rejected\n        setDragOver(null);\n        stopAutoScroll();\n        isInternalDraggingRef.current = false;\n        setIsInternalDragging(false);\n        dragSourceIdsRef.current = [];\n        return;\n      }\n      const valid = isLegalDrop({\n        srcIds: ids,\n        targetId: item.node.id,\n        position: \"inside\",\n        index: nodeIndex,\n      });\n      if (valid && onMove) {\n        onMove({ ids, targetId: item.node.id, position: \"inside\" });\n      }\n      setDragOver(null);\n      stopAutoScroll();\n      isInternalDraggingRef.current = false;\n      setIsInternalDragging(false);\n      dragSourceIdsRef.current = [];\n    },\n    [nodeIndex, onMove, stopAutoScroll],\n  );\n\n  // Container-level (whitespace) handlers\n  const hasFiles = (e: DragEvent) =>\n    Array.from(e.dataTransfer.types).includes(\"Files\");\n\n  const onContainerDragEnter = useCallback(\n    (e: DragEvent) => {\n      if (!enableExternalDrop) return;\n      if (!hasFiles(e)) return;\n      externalEnterCountRef.current += 1;\n      setIsExternalDragging(true);\n    },\n    [enableExternalDrop],\n  );\n\n  const onContainerDragOver = useCallback(\n    (e: DragEvent) => {\n      if (enableExternalDrop && hasFiles(e)) {\n        e.preventDefault();\n        e.dataTransfer.dropEffect = \"copy\";\n      } else if (isInternalDraggingRef.current) {\n        // v0.1.1 (review 5.7): this handler sits on an ANCESTOR of the item\n        // elements, so every item dragover bubbled here and cleared the drop\n        // target the item handler had just set (highlight never rendered,\n        // cursor read \"no-drop\" over legal folders). Only treat the event as\n        // a whitespace hover when it did NOT originate inside an item.\n        const target = e.target as HTMLElement | null;\n        if (target?.closest(\"[data-item-id]\")) return;\n        e.preventDefault();\n        e.dataTransfer.dropEffect = \"none\"; // dropping on whitespace internal = no-op\n        setDragOver(null);\n        // auto-scroll if near edges\n        const scroller = e.currentTarget as HTMLElement;\n        startAutoScroll(scroller, e.clientY);\n      }\n    },\n    [enableExternalDrop, startAutoScroll],\n  );\n\n  const onContainerDragLeave = useCallback(\n    (e: DragEvent) => {\n      if (!enableExternalDrop) return;\n      if (!hasFiles(e)) return;\n      externalEnterCountRef.current = Math.max(\n        0,\n        externalEnterCountRef.current - 1,\n      );\n      if (externalEnterCountRef.current === 0) {\n        setIsExternalDragging(false);\n      }\n    },\n    [enableExternalDrop],\n  );\n\n  const onContainerDrop = useCallback(\n    (e: DragEvent) => {\n      if (enableExternalDrop && hasFiles(e)) {\n        e.preventDefault();\n        externalEnterCountRef.current = 0;\n        setIsExternalDragging(false);\n        const target = e.target as HTMLElement | null;\n        const itemEl = target?.closest(\"[data-item-id]\") as\n          | HTMLElement\n          | null;\n        const itemId = itemEl?.getAttribute(\"data-item-id\") ?? null;\n        let targetFolderId: string | null = currentFolderId;\n        if (itemId) {\n          const node = nodeIndex.get(itemId);\n          if (node?.type === \"folder\") targetFolderId = itemId;\n        }\n        const files = Array.from(e.dataTransfer.files ?? []);\n        if (files.length > 0) {\n          onExternalDrop?.({ files, targetFolderId });\n        }\n        return;\n      }\n      // Internal drag onto whitespace = no-op\n      if (isInternalDraggingRef.current) {\n        e.preventDefault();\n        isInternalDraggingRef.current = false;\n        setIsInternalDragging(false);\n        dragSourceIdsRef.current = [];\n        setDragOver(null);\n        stopAutoScroll();\n      }\n    },\n    [\n      enableExternalDrop,\n      currentFolderId,\n      nodeIndex,\n      onExternalDrop,\n      stopAutoScroll,\n    ],\n  );\n\n  return {\n    dragOver,\n    isExternalDragging,\n    isInternalDragging,\n    onItemDragStart,\n    onItemDragOver,\n    onItemDragLeave,\n    onItemDrop,\n    onItemDragEnd,\n    onContainerDragEnter,\n    onContainerDragOver,\n    onContainerDragLeave,\n    onContainerDrop,\n  };\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/hooks/use-drag.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/hooks/use-keyboard.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport type {\n  FileManagerActions,\n  FileManagerItem,\n  FileManagerOpenArgs,\n  FileManagerViewMode,\n} from \"../types\";\n\ninterface UseKeyboardArgs {\n  items: FileManagerItem[];\n  focusedId: string | null;\n  renamingId: string | null;\n  selectedIds: ReadonlySet<string>;\n  viewMode: FileManagerViewMode;\n  /** Live ref to the current column count in grid mode (computed by content-pane). */\n  columnsRef: React.MutableRefObject<number>;\n  actions: FileManagerActions;\n  onOpen?: (args: FileManagerOpenArgs) => void;\n}\n\nconst SUPPRESS_TAGS = [\"INPUT\", \"TEXTAREA\", \"SELECT\"];\n\nexport interface UseKeyboardResult {\n  onKeyDown: (e: KeyboardEvent<HTMLDivElement>) => void;\n}\n\nexport function useKeyboard(args: UseKeyboardArgs): UseKeyboardResult {\n  const {\n    items,\n    focusedId,\n    renamingId,\n    selectedIds,\n    viewMode,\n    columnsRef,\n    actions,\n    onOpen,\n  } = args;\n\n  // Type-ahead state\n  const typeAheadRef = useRef<{ prefix: string; timer: number | null }>({\n    prefix: \"\",\n    timer: null,\n  });\n\n  // v0.1.1 (review): clear the pending type-ahead reset timer on unmount.\n  // (The ref object's identity is stable — only its fields mutate.)\n  useEffect(() => {\n    const ta = typeAheadRef.current;\n    return () => {\n      if (ta.timer !== null) window.clearTimeout(ta.timer);\n      ta.timer = null;\n      ta.prefix = \"\";\n    };\n  }, []);\n\n  const onKeyDown = useCallback(\n    (e: KeyboardEvent<HTMLDivElement>) => {\n      const target = e.target as HTMLElement | null;\n      if (target && SUPPRESS_TAGS.includes(target.tagName)) return;\n      if (renamingId) return;\n\n      const isMod = e.metaKey || e.ctrlKey;\n      const idx = items.findIndex((item) => item.node.id === focusedId);\n      const noFocus = idx === -1;\n      const cols = Math.max(1, columnsRef.current || 1);\n\n      switch (e.key) {\n        case \"ArrowDown\": {\n          e.preventDefault();\n          if (noFocus) {\n            if (items[0]) actions.focusNode(items[0].node.id);\n            return;\n          }\n          const step = viewMode === \"grid\" ? cols : 1;\n          const next = Math.min(items.length - 1, idx + step);\n          if (items[next]) actions.focusNode(items[next].node.id);\n          return;\n        }\n        case \"ArrowUp\": {\n          e.preventDefault();\n          if (noFocus) {\n            if (items[0]) actions.focusNode(items[0].node.id);\n            return;\n          }\n          const step = viewMode === \"grid\" ? cols : 1;\n          const next = Math.max(0, idx - step);\n          if (items[next]) actions.focusNode(items[next].node.id);\n          return;\n        }\n        case \"ArrowRight\": {\n          if (viewMode !== \"grid\") return;\n          e.preventDefault();\n          if (noFocus) {\n            if (items[0]) actions.focusNode(items[0].node.id);\n            return;\n          }\n          const next = Math.min(items.length - 1, idx + 1);\n          if (items[next]) actions.focusNode(items[next].node.id);\n          return;\n        }\n        case \"ArrowLeft\": {\n          if (viewMode !== \"grid\") return;\n          e.preventDefault();\n          if (noFocus) {\n            if (items[0]) actions.focusNode(items[0].node.id);\n            return;\n          }\n          const next = Math.max(0, idx - 1);\n          if (items[next]) actions.focusNode(items[next].node.id);\n          return;\n        }\n        case \"Home\": {\n          e.preventDefault();\n          if (items[0]) actions.focusNode(items[0].node.id);\n          return;\n        }\n        case \"End\": {\n          e.preventDefault();\n          const last = items[items.length - 1];\n          if (last) actions.focusNode(last.node.id);\n          return;\n        }\n        case \"Enter\": {\n          if (noFocus) return;\n          e.preventDefault();\n          const item = items[idx];\n          if (item.node.type === \"folder\") {\n            actions.navigateTo(item.node.id);\n          } else {\n            onOpen?.({ node: item.node });\n          }\n          return;\n        }\n        case \" \":\n        case \"Spacebar\": {\n          if (noFocus) return;\n          e.preventDefault();\n          actions.toggleSelection(items[idx].node.id);\n          return;\n        }\n        case \"F2\": {\n          if (noFocus) return;\n          e.preventDefault();\n          actions.startRename(items[idx].node.id);\n          return;\n        }\n        case \"Delete\": {\n          if (selectedIds.size === 0) return;\n          e.preventDefault();\n          actions.triggerDelete(Array.from(selectedIds));\n          return;\n        }\n        case \"Backspace\": {\n          if (selectedIds.size > 0) {\n            e.preventDefault();\n            actions.triggerDelete(Array.from(selectedIds));\n          } else {\n            e.preventDefault();\n            actions.navigateUp();\n          }\n          return;\n        }\n        case \"Escape\": {\n          if (selectedIds.size > 0) {\n            e.preventDefault();\n            actions.clearSelection();\n          }\n          return;\n        }\n        case \"a\":\n        case \"A\": {\n          if (!isMod) break;\n          e.preventDefault();\n          actions.selectAll();\n          return;\n        }\n        case \"x\":\n        case \"X\": {\n          if (!isMod) break;\n          if (selectedIds.size === 0) return;\n          e.preventDefault();\n          actions.cut(Array.from(selectedIds));\n          return;\n        }\n        case \"c\":\n        case \"C\": {\n          if (!isMod) break;\n          if (selectedIds.size === 0) return;\n          e.preventDefault();\n          actions.copy(Array.from(selectedIds));\n          return;\n        }\n        case \"v\":\n        case \"V\": {\n          if (!isMod) break;\n          e.preventDefault();\n          actions.paste();\n          return;\n        }\n        case \"[\": {\n          if (!isMod) break;\n          e.preventDefault();\n          actions.navigateBack();\n          return;\n        }\n        case \"]\": {\n          if (!isMod) break;\n          e.preventDefault();\n          actions.navigateForward();\n          return;\n        }\n      }\n\n      // Type-ahead: a single printable character (no modifier)\n      if (!isMod && e.key.length === 1 && /\\S/.test(e.key)) {\n        const ta = typeAheadRef.current;\n        ta.prefix += e.key.toLowerCase();\n        if (ta.timer !== null) window.clearTimeout(ta.timer);\n        ta.timer = window.setTimeout(() => {\n          ta.prefix = \"\";\n          ta.timer = null;\n        }, 800);\n        const match = items.find((item) =>\n          item.node.name.toLowerCase().startsWith(ta.prefix),\n        );\n        if (match) {\n          e.preventDefault();\n          actions.focusNode(match.node.id);\n        }\n      }\n    },\n    [\n      items,\n      focusedId,\n      renamingId,\n      selectedIds,\n      viewMode,\n      columnsRef,\n      actions,\n      onOpen,\n    ],\n  );\n\n  return { onKeyDown };\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/hooks/use-keyboard.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/lib/icons.tsx",
      "content": "import type { ReactNode } from \"react\";\nimport {\n  File,\n  FileArchive,\n  FileAudio,\n  FileCode,\n  FileCode2,\n  FileImage,\n  FileJson,\n  FileText,\n  FileVideo,\n  Folder,\n  FolderOpen,\n} from \"lucide-react\";\n\nimport type { FsNode } from \"../types\";\n\nconst EXT_TO_ICON: Record<string, typeof File> = {\n  ts: FileCode,\n  tsx: FileCode,\n  js: FileCode,\n  jsx: FileCode,\n  mjs: FileCode,\n  cjs: FileCode,\n  py: FileCode,\n  rb: FileCode,\n  go: FileCode,\n  rs: FileCode,\n  java: FileCode,\n  kt: FileCode,\n  swift: FileCode,\n  cpp: FileCode,\n  c: FileCode,\n  h: FileCode,\n  cs: FileCode,\n  php: FileCode,\n  lua: FileCode,\n  css: FileCode,\n  scss: FileCode,\n  sass: FileCode,\n  less: FileCode,\n  html: FileCode2,\n  htm: FileCode2,\n  xml: FileCode2,\n  json: FileJson,\n  jsonc: FileJson,\n  md: FileText,\n  mdx: FileText,\n  markdown: FileText,\n  txt: FileText,\n  rtf: FileText,\n  png: FileImage,\n  jpg: FileImage,\n  jpeg: FileImage,\n  gif: FileImage,\n  webp: FileImage,\n  bmp: FileImage,\n  ico: FileImage,\n  svg: FileImage,\n  zip: FileArchive,\n  tar: FileArchive,\n  gz: FileArchive,\n  \"7z\": FileArchive,\n  rar: FileArchive,\n  mp4: FileVideo,\n  mov: FileVideo,\n  webm: FileVideo,\n  mkv: FileVideo,\n  avi: FileVideo,\n  mp3: FileAudio,\n  wav: FileAudio,\n  ogg: FileAudio,\n  flac: FileAudio,\n  lock: File,\n  env: File,\n  gitignore: File,\n  gitattributes: File,\n  editorconfig: File,\n};\n\nexport function getNodeExtension(node: FsNode): string {\n  if (node.ext) return node.ext.replace(/^\\./, \"\").toLowerCase();\n  const idx = node.name.lastIndexOf(\".\");\n  if (idx <= 0 || idx === node.name.length - 1) return \"\";\n  return node.name.slice(idx + 1).toLowerCase();\n}\n\nexport function iconForExtension(\n  ext: string,\n  className = \"size-4\",\n): ReactNode {\n  const normalized = ext.replace(/^\\./, \"\").toLowerCase();\n  const Icon = EXT_TO_ICON[normalized] ?? File;\n  return <Icon className={className} />;\n}\n\nexport function defaultFileIcon(node: FsNode, className = \"size-4\"): ReactNode {\n  return iconForExtension(getNodeExtension(node), className);\n}\n\nexport function defaultFolderIcon(\n  expanded: boolean,\n  className = \"size-4\",\n): ReactNode {\n  const Icon = expanded ? FolderOpen : Folder;\n  return <Icon className={className} />;\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/lib/icons.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/lib/tree-utils.ts",
      "content": "import type { FsNode } from \"../types\";\n\n/** Flat-index a hierarchical array by id. */\nexport function indexNodes(nodes: FsNode[]): Map<string, FsNode> {\n  const map = new Map<string, FsNode>();\n  const walk = (list: FsNode[]) => {\n    for (const node of list) {\n      if (map.has(node.id)) continue;\n      map.set(node.id, node);\n      if (node.children) walk(node.children);\n    }\n  };\n  walk(nodes);\n  return map;\n}\n\nexport function findNode(\n  id: string,\n  nodes: FsNode[],\n): FsNode | undefined {\n  for (const node of nodes) {\n    if (node.id === id) return node;\n    if (node.children) {\n      const hit = findNode(id, node.children);\n      if (hit) return hit;\n    }\n  }\n  return undefined;\n}\n\nexport function getParentChain(\n  nodeId: string,\n  index: Map<string, FsNode>,\n): FsNode[] {\n  const chain: FsNode[] = [];\n  let current = index.get(nodeId);\n  while (current?.parentId) {\n    const parent = index.get(current.parentId);\n    if (!parent) break;\n    chain.unshift(parent);\n    current = parent;\n  }\n  return chain;\n}\n\nexport function getDescendantIds(\n  nodeId: string,\n  index: Map<string, FsNode>,\n): Set<string> {\n  const out = new Set<string>();\n  const node = index.get(nodeId);\n  if (!node?.children) return out;\n  const walk = (list: FsNode[]) => {\n    for (const child of list) {\n      if (out.has(child.id)) continue;\n      out.add(child.id);\n      if (child.children) walk(child.children);\n    }\n  };\n  walk(node.children);\n  return out;\n}\n\n/** Default sort: folders first, then case-insensitive locale alpha by name. */\nexport function defaultSort(a: FsNode, b: FsNode): number {\n  if (a.type !== b.type) return a.type === \"folder\" ? -1 : 1;\n  return a.name.localeCompare(b.name, undefined, { sensitivity: \"base\" });\n}\n\n/** Children of a given folder id; null = root-level nodes. */\nexport function getChildrenOf(\n  folderId: string | null,\n  nodes: FsNode[],\n  index: Map<string, FsNode>,\n): FsNode[] | undefined {\n  if (folderId === null) return nodes;\n  const folder = index.get(folderId);\n  if (!folder) return undefined;\n  if (folder.type !== \"folder\") return undefined;\n  return folder.children;\n}\n\nfunction replaceChildren(\n  nodes: FsNode[],\n  parentId: string,\n  next: FsNode[],\n): FsNode[] {\n  let changed = false;\n  const out = nodes.map((node) => {\n    if (node.id === parentId) {\n      changed = true;\n      return { ...node, children: next };\n    }\n    if (node.children) {\n      const replaced = replaceChildren(node.children, parentId, next);\n      if (replaced !== node.children) {\n        changed = true;\n        return { ...node, children: replaced };\n      }\n    }\n    return node;\n  });\n  return changed ? out : nodes;\n}\n\n/** Public consumer helper for splicing lazy-loaded children into the tree. */\nexport function mergeLoadedChildren(\n  nodes: FsNode[],\n  parentId: string,\n  children: FsNode[],\n): FsNode[] {\n  return replaceChildren(nodes, parentId, children);\n}\n\nexport function countAllNodes(nodes: FsNode[]): number {\n  let total = 0;\n  const walk = (list: FsNode[]) => {\n    for (const node of list) {\n      total += 1;\n      if (node.children) walk(node.children);\n    }\n  };\n  walk(nodes);\n  return total;\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/lib/tree-utils.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/lib/validation.ts",
      "content": "import type { FsNode, FileManagerDropPosition } from \"../types\";\n\nexport function isSelfDrop(\n  srcIds: string[],\n  targetId: string | null,\n): boolean {\n  if (!targetId) return false;\n  return srcIds.includes(targetId);\n}\n\nexport function isCycle(\n  srcIds: string[],\n  targetId: string | null,\n  index: Map<string, FsNode>,\n): boolean {\n  if (!targetId) return false;\n  const set = new Set(srcIds);\n  let current: FsNode | undefined = index.get(targetId);\n  while (current) {\n    if (set.has(current.id)) return true;\n    const parentId = current.parentId;\n    if (!parentId) break;\n    current = index.get(parentId);\n  }\n  return false;\n}\n\nexport interface IsLegalDropArgs {\n  srcIds: string[];\n  targetId: string | null;\n  position: FileManagerDropPosition;\n  index: Map<string, FsNode>;\n}\n\nexport function isLegalDrop({\n  srcIds,\n  targetId,\n  position,\n  index,\n}: IsLegalDropArgs): boolean {\n  if (srcIds.length === 0) return false;\n  if (isSelfDrop(srcIds, targetId)) return false;\n  if (position === \"inside\" && isCycle(srcIds, targetId, index)) return false;\n  return true;\n}\n\n/** Soft-validate a node tree on mount. */\nexport function validateNodes(nodes: FsNode[]): string[] {\n  const warnings: string[] = [];\n  const ids = new Set<string>();\n  const walk = (list: FsNode[], path: Set<string>) => {\n    for (const node of list) {\n      if (ids.has(node.id)) {\n        warnings.push(`duplicate node id: \"${node.id}\"`);\n        continue;\n      }\n      ids.add(node.id);\n      if (path.has(node.id)) {\n        warnings.push(`cycle detected at node: \"${node.id}\"`);\n        continue;\n      }\n      if (node.children) {\n        const next = new Set(path);\n        next.add(node.id);\n        walk(node.children, next);\n      }\n    }\n  };\n  walk(nodes, new Set());\n  return warnings;\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/lib/validation.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/lib/path.ts",
      "content": "import type { FsNode } from \"../types\";\nimport { getParentChain } from \"./tree-utils\";\n\n/**\n * Build the path from root → ... → currentFolder. Empty array when at root\n * (`currentFolderId === null`). Returns the chain of FsNodes.\n */\nexport function buildPath(\n  currentFolderId: string | null,\n  index: Map<string, FsNode>,\n): FsNode[] {\n  if (currentFolderId === null) return [];\n  const current = index.get(currentFolderId);\n  if (!current) return [];\n  const chain = getParentChain(currentFolderId, index);\n  return [...chain, current];\n}\n\n/**\n * Best-effort string-to-id resolution. Looks for a node whose name path\n * (from root) matches the typed segments. Returns the id of the matching\n * folder, or `null` if no match.\n *\n * Consumer typically provides their own resolver via `onPathTyped`; this\n * helper is for the simple case where path segments map directly to node\n * names.\n */\nexport function parsePath(\n  path: string,\n  nodes: FsNode[],\n): string | null {\n  const segments = path.split(/[/\\\\]/).filter(Boolean);\n  if (segments.length === 0) return null;\n\n  let current: FsNode[] | undefined = nodes;\n  let lastMatchId: string | null = null;\n  for (const segment of segments) {\n    if (!current) return null;\n    const match: FsNode | undefined = current.find(\n      (n: FsNode) =>\n        n.type === \"folder\" &&\n        n.name.localeCompare(segment, undefined, { sensitivity: \"base\" }) ===\n          0,\n    );\n    if (!match) return null;\n    lastMatchId = match.id;\n    current = match.children;\n  }\n  return lastMatchId;\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/lib/path.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/lib/intersect.ts",
      "content": "export interface Rect {\n  left: number;\n  top: number;\n  right: number;\n  bottom: number;\n}\n\n/** AABB rectangle intersection. */\nexport function rectanglesIntersect(a: Rect, b: Rect): boolean {\n  return !(\n    a.right < b.left ||\n    a.left > b.right ||\n    a.bottom < b.top ||\n    a.top > b.bottom\n  );\n}\n\n/**\n * Collect bounding rects of every item currently rendered inside `scroller`,\n * keyed by `data-item-id`. Used by the marquee hook to determine which items\n * the rectangle currently covers.\n *\n * Rects are in viewport coordinates (`getBoundingClientRect`); callers\n * comparing against the marquee rectangle should use the same coordinate\n * space.\n */\nexport function getItemRects(\n  scroller: HTMLElement | null,\n): Map<string, Rect> {\n  const out = new Map<string, Rect>();\n  if (!scroller) return out;\n  const items = scroller.querySelectorAll<HTMLElement>(\"[data-item-id]\");\n  for (const el of items) {\n    const id = el.getAttribute(\"data-item-id\");\n    if (!id) continue;\n    const rect = el.getBoundingClientRect();\n    out.set(id, {\n      left: rect.left,\n      top: rect.top,\n      right: rect.right,\n      bottom: rect.bottom,\n    });\n  }\n  return out;\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/lib/intersect.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/lib/format.ts",
      "content": "/** Format bytes as a human-readable string (\"4.2 MB\", \"812 KB\", \"32 B\"). */\nexport function formatBytes(bytes: number): string {\n  if (!Number.isFinite(bytes) || bytes < 0) return \"—\";\n  if (bytes === 0) return \"0 B\";\n  const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n  let value = bytes;\n  let unit = 0;\n  while (value >= 1024 && unit < units.length - 1) {\n    value /= 1024;\n    unit += 1;\n  }\n  const rounded = value < 10 ? value.toFixed(1) : Math.round(value).toString();\n  return `${rounded} ${units[unit]}`;\n}\n\nconst dateFormatter = new Intl.DateTimeFormat(undefined, {\n  year: \"numeric\",\n  month: \"short\",\n  day: \"numeric\",\n});\n\n/** Format an ISO date string as \"May 9, 2026\". Returns the input if unparseable. */\nexport function formatDate(iso: string | undefined): string {\n  if (!iso) return \"—\";\n  const date = new Date(iso);\n  if (Number.isNaN(date.getTime())) return iso;\n  return dateFormatter.format(date);\n}\n\n/** Capitalize an extension for display in the list view's Type column. */\nexport function formatKind(ext: string, type: \"file\" | \"folder\"): string {\n  if (type === \"folder\") return \"Folder\";\n  if (!ext) return \"File\";\n  return ext.toUpperCase();\n}\n\n/** Plural-aware count: \"1 item\" / \"5 items\". */\nexport function formatItemCount(n: number, label = \"items\"): string {\n  return `${n} ${n === 1 ? label.replace(/s$/, \"\") : label}`;\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/lib/format.ts"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-toolbar.tsx",
      "content": "\"use client\";\n\nimport type { FileManagerPathTypedArgs } from \"../types\";\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\nimport { FileManagerBackForward } from \"./file-manager-back-forward\";\nimport { FileManagerUpButton } from \"./file-manager-up-button\";\nimport { FileManagerPathBar } from \"./file-manager-path-bar\";\nimport { FileManagerViewToggle } from \"./file-manager-view-toggle\";\nimport { FileManagerIconSizeControl } from \"./file-manager-icon-size-control\";\nimport { FileManagerSortMenu } from \"./file-manager-sort-menu\";\nimport { FileManagerSearchInput } from \"./file-manager-search-input\";\nimport { FileManagerNewButtons } from \"./file-manager-new-buttons\";\nimport { FileManagerRefreshButton } from \"./file-manager-refresh-button\";\n\nexport interface FileManagerToolbarProps {\n  onPathTyped?: (args: FileManagerPathTypedArgs) => void;\n}\n\nexport function FileManagerToolbar(props: FileManagerToolbarProps) {\n  const { onPathTyped } = props;\n  const {\n    showBackForward,\n    showUpButton,\n    showPathBar,\n    showSearch,\n    showSort,\n    showIconSize,\n    showViewToggle,\n    showNewFile,\n    showNewFolder,\n    showRefresh,\n  } = useFileManager();\n\n  return (\n    <div\n      role=\"toolbar\"\n      className=\"flex h-10 shrink-0 items-center gap-1 border-b border-border/60 bg-card/40 px-2\"\n    >\n      {showBackForward ? <FileManagerBackForward /> : null}\n      {showUpButton ? <FileManagerUpButton /> : null}\n      {showPathBar ? <FileManagerPathBar onPathTyped={onPathTyped} /> : null}\n      {showSearch ? <FileManagerSearchInput /> : null}\n      {showSort ? <FileManagerSortMenu /> : null}\n      {showIconSize ? <FileManagerIconSizeControl /> : null}\n      {showViewToggle ? <FileManagerViewToggle /> : null}\n      {(showNewFile || showNewFolder) ? <FileManagerNewButtons /> : null}\n      {showRefresh ? <FileManagerRefreshButton /> : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-toolbar.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-back-forward.tsx",
      "content": "\"use client\";\n\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { buttonVariants } from \"@/components/ui/button\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\n\nexport function FileManagerBackForward() {\n  const { actions, state, labels } = useFileManager();\n  const canBack = state.historyBackIds.length > 0;\n  const canForward = state.historyForwardIds.length > 0;\n  // F-cross-13: no `asChild` — Base UI triggers reject it; triggers ARE the buttons, styled via buttonVariants.\n  const triggerClass = cn(\n    buttonVariants({ variant: \"ghost\", size: \"icon\" }),\n    \"size-7\",\n  );\n  return (\n    <div className=\"flex shrink-0 items-center gap-0.5\">\n      <Tooltip>\n        <TooltipTrigger\n          type=\"button\"\n          className={triggerClass}\n          disabled={!canBack}\n          onClick={() => actions.navigateBack()}\n          aria-label={labels.back}\n        >\n          <ChevronLeft className=\"size-4\" aria-hidden=\"true\" />\n        </TooltipTrigger>\n        <TooltipContent>{labels.back}</TooltipContent>\n      </Tooltip>\n      <Tooltip>\n        <TooltipTrigger\n          type=\"button\"\n          className={triggerClass}\n          disabled={!canForward}\n          onClick={() => actions.navigateForward()}\n          aria-label={labels.forward}\n        >\n          <ChevronRight className=\"size-4\" aria-hidden=\"true\" />\n        </TooltipTrigger>\n        <TooltipContent>{labels.forward}</TooltipContent>\n      </Tooltip>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-back-forward.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-up-button.tsx",
      "content": "\"use client\";\n\nimport { ArrowUp } from \"lucide-react\";\nimport { buttonVariants } from \"@/components/ui/button\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\n\nexport function FileManagerUpButton() {\n  const { actions, state, labels } = useFileManager();\n  const atRoot = state.currentFolderId === null;\n  // F-cross-13: no `asChild` — Base UI triggers reject it; the trigger IS the button, styled via buttonVariants.\n  return (\n    <Tooltip>\n      <TooltipTrigger\n        type=\"button\"\n        className={cn(buttonVariants({ variant: \"ghost\", size: \"icon\" }), \"size-7\")}\n        disabled={atRoot}\n        onClick={() => actions.navigateUp()}\n        aria-label={labels.up}\n      >\n        <ArrowUp className=\"size-4\" aria-hidden=\"true\" />\n      </TooltipTrigger>\n      <TooltipContent>{labels.up}</TooltipContent>\n    </Tooltip>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-up-button.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-path-bar.tsx",
      "content": "\"use client\";\n\nimport {\n  useEffect,\n  useRef,\n  useState,\n  type KeyboardEvent,\n  type MouseEvent as ReactMouseEvent,\n} from \"react\";\nimport { ChevronRight, Pencil } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\nimport type { FileManagerPathTypedArgs } from \"../types\";\n\nexport interface FileManagerPathBarProps {\n  /** Optional callback when user types a path; if omitted, the bar stays in display mode. */\n  onPathTyped?: (args: FileManagerPathTypedArgs) => void;\n  className?: string;\n}\n\nexport function FileManagerPathBar(props: FileManagerPathBarProps) {\n  const { onPathTyped, className } = props;\n  const { state, actions, labels } = useFileManager();\n  const { path } = state;\n  const [editing, setEditing] = useState(false);\n  const [draft, setDraft] = useState(\"\");\n  const inputRef = useRef<HTMLInputElement>(null);\n\n  useEffect(() => {\n    if (editing) inputRef.current?.focus();\n  }, [editing]);\n\n  const display = path.map((n) => n.name).join(\"/\");\n\n  const startEdit = (e: ReactMouseEvent) => {\n    if (!onPathTyped) return;\n    // Only enter edit mode when clicking the empty area (not a segment)\n    const target = e.target as HTMLElement | null;\n    if (target?.closest(\"[data-path-segment]\")) return;\n    setDraft(display);\n    setEditing(true);\n  };\n\n  const commit = () => {\n    if (draft.trim() === display.trim()) {\n      setEditing(false);\n      return;\n    }\n    onPathTyped?.({ path: draft });\n    setEditing(false);\n  };\n\n  const onInputKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n    if (e.key === \"Enter\") {\n      e.preventDefault();\n      commit();\n    } else if (e.key === \"Escape\") {\n      e.preventDefault();\n      setEditing(false);\n    }\n  };\n\n  if (editing) {\n    return (\n      <div className={cn(\"flex flex-1 items-center\", className)}>\n        <input\n          ref={inputRef}\n          type=\"text\"\n          value={draft}\n          onChange={(e) => setDraft(e.target.value)}\n          onBlur={commit}\n          onKeyDown={onInputKeyDown}\n          className=\"h-7 w-full rounded-sm border border-border bg-background px-2 font-mono text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n          aria-label=\"Path\"\n        />\n      </div>\n    );\n  }\n\n  return (\n    <nav\n      aria-label=\"Path\"\n      onClick={startEdit}\n      className={cn(\n        \"flex min-w-0 flex-1 cursor-text items-center gap-0.5 truncate rounded-sm px-1 py-0.5 text-xs\",\n        onPathTyped && \"hover:bg-muted/40\",\n        className,\n      )}\n    >\n      <button\n        type=\"button\"\n        data-path-segment=\"root\"\n        onClick={(e) => {\n          e.stopPropagation();\n          actions.navigateTo(null);\n        }}\n        className=\"rounded-sm px-1 py-0.5 font-medium text-muted-foreground hover:bg-muted/60 hover:text-foreground\"\n      >\n        {labels.pathRoot}\n      </button>\n      {path.map((segment) => (\n        <span key={segment.id} className=\"flex shrink-0 items-center gap-0.5\">\n          <ChevronRight\n            className=\"size-3 text-muted-foreground/60\"\n            aria-hidden=\"true\"\n          />\n          <button\n            type=\"button\"\n            data-path-segment={segment.id}\n            onClick={(e) => {\n              e.stopPropagation();\n              actions.navigateTo(segment.id);\n            }}\n            className=\"max-w-[180px] truncate rounded-sm px-1 py-0.5 text-foreground hover:bg-muted/60\"\n          >\n            {segment.name}\n          </button>\n        </span>\n      ))}\n      {onPathTyped ? (\n        <Pencil\n          aria-hidden=\"true\"\n          className=\"ml-auto size-3 shrink-0 text-muted-foreground/40\"\n        />\n      ) : null}\n    </nav>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-path-bar.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-view-toggle.tsx",
      "content": "\"use client\";\n\nimport { LayoutGrid, List } from \"lucide-react\";\nimport { buttonVariants } from \"@/components/ui/button\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\nimport type { FileManagerViewMode } from \"../types\";\n\n/**\n * Plain-button segmented control. Deliberately NOT shadcn ToggleGroup — its\n * value model diverges between Radix (single: string) and Base UI (string[]),\n * an F-cross-13 carrier (event-calendar v0.2.1 precedent; toggle-group dep\n * dropped). Each segment is the TooltipTrigger itself — no `asChild`, which\n * Base UI triggers reject.\n */\nexport function FileManagerViewToggle() {\n  const { actions, state, labels } = useFileManager();\n  const modes: Array<{\n    mode: FileManagerViewMode;\n    label: string;\n    Icon: typeof LayoutGrid;\n  }> = [\n    { mode: \"grid\", label: labels.viewGrid, Icon: LayoutGrid },\n    { mode: \"list\", label: labels.viewList, Icon: List },\n  ];\n  return (\n    <div role=\"group\" className=\"flex shrink-0 items-center\">\n      {modes.map(({ mode, label, Icon }) => (\n        <Tooltip key={mode}>\n          <TooltipTrigger\n            type=\"button\"\n            aria-pressed={state.viewMode === mode}\n            aria-label={label}\n            onClick={() => actions.setViewMode(mode)}\n            className={cn(\n              buttonVariants({ variant: \"ghost\", size: \"icon\" }),\n              \"size-7 rounded-none first:rounded-l-lg last:rounded-r-lg\",\n              state.viewMode === mode && \"bg-muted\",\n            )}\n          >\n            <Icon className=\"size-4\" aria-hidden=\"true\" />\n          </TooltipTrigger>\n          <TooltipContent>{label}</TooltipContent>\n        </Tooltip>\n      ))}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-view-toggle.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-icon-size-control.tsx",
      "content": "\"use client\";\n\nimport { buttonVariants } from \"@/components/ui/button\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\nimport type { FileManagerIconSize } from \"../types\";\n\n/**\n * Plain-button segmented control. Deliberately NOT shadcn ToggleGroup — its\n * value model diverges between Radix (single: string) and Base UI (string[]),\n * an F-cross-13 carrier (event-calendar v0.2.1 precedent; toggle-group dep\n * dropped). Each segment is the TooltipTrigger itself — no `asChild`, which\n * Base UI triggers reject.\n */\nexport function FileManagerIconSizeControl() {\n  const { actions, state, labels } = useFileManager();\n  if (state.viewMode !== \"grid\") return null;\n\n  const dotSize: Record<FileManagerIconSize, string> = {\n    sm: \"size-1.5\",\n    md: \"size-2\",\n    lg: \"size-2.5\",\n  };\n\n  return (\n    <div role=\"group\" className=\"flex shrink-0 items-center\">\n      {([\"sm\", \"md\", \"lg\"] as const).map((size) => {\n        const label =\n          size === \"sm\"\n            ? labels.iconSizeSmall\n            : size === \"md\"\n              ? labels.iconSizeMedium\n              : labels.iconSizeLarge;\n        return (\n          <Tooltip key={size}>\n            <TooltipTrigger\n              type=\"button\"\n              aria-pressed={state.iconSize === size}\n              aria-label={label}\n              onClick={() => actions.setIconSize(size)}\n              className={cn(\n                buttonVariants({ variant: \"ghost\", size: \"icon\" }),\n                \"size-7 rounded-none first:rounded-l-lg last:rounded-r-lg\",\n                state.iconSize === size && \"bg-muted\",\n              )}\n            >\n              <span\n                className={`rounded-full bg-current ${dotSize[size]}`}\n                aria-hidden=\"true\"\n              />\n            </TooltipTrigger>\n            <TooltipContent>{label}</TooltipContent>\n          </Tooltip>\n        );\n      })}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-icon-size-control.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-sort-menu.tsx",
      "content": "\"use client\";\n\nimport { ArrowDown, ArrowUp, ArrowUpDown, Check } from \"lucide-react\";\nimport { buttonVariants } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { cn } from \"@/lib/utils\";\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\nimport type { FileManagerSortKey } from \"../types\";\n\nexport function FileManagerSortMenu() {\n  const { actions, state, labels } = useFileManager();\n  const sortKeys: { key: FileManagerSortKey; label: string }[] = [\n    { key: \"name\", label: labels.sortByName },\n    { key: \"modified\", label: labels.sortByModified },\n    { key: \"size\", label: labels.sortBySize },\n    { key: \"type\", label: labels.sortByType },\n  ];\n\n  // F-cross-13: no `asChild` — Base UI triggers reject it. The Tooltip wrapper\n  // is dropped: TooltipTrigger renders its own <button> in both backends, and\n  // nesting it with DropdownMenuTrigger would need asChild/render to fuse them\n  // into one element. `title` keeps a hover affordance natively.\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        type=\"button\"\n        className={cn(buttonVariants({ variant: \"ghost\", size: \"icon\" }), \"size-7\")}\n        aria-label={`Sort by ${state.sort.key}`}\n        title=\"Sort\"\n      >\n        <ArrowUpDown className=\"size-4\" aria-hidden=\"true\" />\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" className=\"min-w-[160px]\">\n        <DropdownMenuLabel>Sort by</DropdownMenuLabel>\n        {sortKeys.map(({ key, label }) => (\n          <DropdownMenuItem\n            key={key}\n            onSelect={() =>\n              actions.setSort({\n                key,\n                order:\n                  state.sort.key === key && state.sort.order === \"asc\"\n                    ? \"desc\"\n                    : \"asc\",\n              })\n            }\n          >\n            {state.sort.key === key ? (\n              <Check className=\"size-3.5\" aria-hidden=\"true\" />\n            ) : (\n              <span aria-hidden=\"true\" className=\"size-3.5\" />\n            )}\n            {label}\n          </DropdownMenuItem>\n        ))}\n        <DropdownMenuSeparator />\n        <DropdownMenuItem\n          onSelect={() =>\n            actions.setSort({\n              key: state.sort.key,\n              order: state.sort.order === \"asc\" ? \"desc\" : \"asc\",\n            })\n          }\n        >\n          {state.sort.order === \"asc\" ? (\n            <ArrowUp className=\"size-3.5\" aria-hidden=\"true\" />\n          ) : (\n            <ArrowDown className=\"size-3.5\" aria-hidden=\"true\" />\n          )}\n          {state.sort.order === \"asc\" ? labels.sortAsc : labels.sortDesc}\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-sort-menu.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-search-input.tsx",
      "content": "\"use client\";\n\nimport { Search, X } from \"lucide-react\";\nimport { Input } from \"@/components/ui/input\";\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\n\nexport function FileManagerSearchInput() {\n  const { actions, state, labels } = useFileManager();\n  return (\n    <div className=\"relative flex w-48 shrink-0 items-center\">\n      <Search\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute left-2 size-3.5 text-muted-foreground\"\n      />\n      <Input\n        type=\"search\"\n        value={state.searchQuery}\n        onChange={(e) => actions.setSearchQuery(e.target.value)}\n        placeholder={labels.searchPlaceholder}\n        aria-label={labels.searchPlaceholder}\n        className=\"h-7 pl-7 pr-7 text-xs\"\n      />\n      {state.searchQuery ? (\n        <button\n          type=\"button\"\n          onClick={() => actions.setSearchQuery(\"\")}\n          aria-label=\"Clear search\"\n          className=\"absolute right-2 rounded-sm text-muted-foreground hover:text-foreground\"\n        >\n          <X className=\"size-3.5\" aria-hidden=\"true\" />\n        </button>\n      ) : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-search-input.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-new-buttons.tsx",
      "content": "\"use client\";\n\nimport { FilePlus, FolderPlus } from \"lucide-react\";\nimport { buttonVariants } from \"@/components/ui/button\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\n\nexport function FileManagerNewButtons() {\n  const { actions, showNewFile, showNewFolder, labels } = useFileManager();\n  if (!showNewFile && !showNewFolder) return null;\n  // F-cross-13: no `asChild` — Base UI triggers reject it; triggers ARE the buttons, styled via buttonVariants.\n  const triggerClass = cn(\n    buttonVariants({ variant: \"ghost\", size: \"icon\" }),\n    \"size-7\",\n  );\n  return (\n    <div className=\"flex shrink-0 items-center gap-0.5\">\n      {showNewFile ? (\n        <Tooltip>\n          <TooltipTrigger\n            type=\"button\"\n            className={triggerClass}\n            onClick={() => actions.triggerCreate(\"file\")}\n            aria-label={labels.newFile}\n          >\n            <FilePlus className=\"size-4\" aria-hidden=\"true\" />\n          </TooltipTrigger>\n          <TooltipContent>{labels.newFile}</TooltipContent>\n        </Tooltip>\n      ) : null}\n      {showNewFolder ? (\n        <Tooltip>\n          <TooltipTrigger\n            type=\"button\"\n            className={triggerClass}\n            onClick={() => actions.triggerCreate(\"folder\")}\n            aria-label={labels.newFolder}\n          >\n            <FolderPlus className=\"size-4\" aria-hidden=\"true\" />\n          </TooltipTrigger>\n          <TooltipContent>{labels.newFolder}</TooltipContent>\n        </Tooltip>\n      ) : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-new-buttons.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-refresh-button.tsx",
      "content": "\"use client\";\n\nimport { RefreshCw } from \"lucide-react\";\nimport { buttonVariants } from \"@/components/ui/button\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\n\nexport function FileManagerRefreshButton() {\n  const { actions, state, labels, showRefresh } = useFileManager();\n  if (!showRefresh) return null;\n  // F-cross-13: no `asChild` — Base UI triggers reject it; the trigger IS the button, styled via buttonVariants.\n  return (\n    <Tooltip>\n      <TooltipTrigger\n        type=\"button\"\n        className={cn(buttonVariants({ variant: \"ghost\", size: \"icon\" }), \"size-7\")}\n        onClick={() => actions.refresh(state.currentFolderId)}\n        aria-label={labels.refresh}\n      >\n        <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n      </TooltipTrigger>\n      <TooltipContent>{labels.refresh}</TooltipContent>\n    </Tooltip>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-refresh-button.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-content-pane.tsx",
      "content": "\"use client\";\n\nimport {\n  useCallback,\n  useEffect,\n  type DragEvent,\n  type MouseEvent,\n  type PointerEvent,\n  type ReactNode,\n  type RefObject,\n} from \"react\";\nimport type {\n  FileManagerIconSize,\n  FileManagerItem,\n  FileManagerItemRenderArgs,\n  FileManagerLabels,\n  FileManagerSortState,\n  FileManagerValidateRenameArgs,\n  FileManagerViewMode,\n  FileManagerVirtualizeMode,\n  FsNode,\n} from \"../types\";\nimport type { MarqueeRect } from \"../hooks/use-marquee\";\nimport { FileManagerGridView } from \"./file-manager-grid-view\";\nimport { FileManagerListView } from \"./file-manager-list-view\";\nimport { FileManagerMarquee } from \"./file-manager-marquee\";\nimport { FileManagerEmpty } from \"./file-manager-empty\";\nimport { FileManagerLoading } from \"./file-manager-loading\";\n\ninterface FileManagerContentPaneProps {\n  items: FileManagerItem[];\n  viewMode: FileManagerViewMode;\n  iconSize: FileManagerIconSize;\n  rowHeight: number;\n  sort: FileManagerSortState;\n  onSetSort: (next: FileManagerSortState) => void;\n  renamingId: string | null;\n  validateRename?: (args: FileManagerValidateRenameArgs) => string | null;\n  iconForNode?: (args: { node: FsNode }) => ReactNode;\n  enableInternalDrag: boolean;\n  enableMarqueeSelection: boolean;\n  dropTargetId: string | null;\n  dropInvalid: boolean;\n  loading: boolean;\n  loadError: string | null;\n  onRetryLoad: () => void;\n  isEmpty: boolean;\n  marqueeRect: MarqueeRect | null;\n  marqueeActive: boolean;\n  onMarqueePointerDown: (e: PointerEvent<HTMLDivElement>) => void;\n  scrollRef: RefObject<HTMLDivElement | null>;\n  /** Computed columns ref for keyboard 2D-arrow nav. */\n  columnsRef: React.MutableRefObject<number>;\n  /** Virtualization config — applied by list view internally. */\n  virtualize: FileManagerVirtualizeMode;\n  virtualizeThreshold: number;\n  renderItem?: (args: FileManagerItemRenderArgs) => ReactNode;\n  renderEmpty?: () => ReactNode;\n  renderLoading?: () => ReactNode;\n  labels: FileManagerLabels;\n  // Item-level callbacks\n  onItemClick: (e: MouseEvent, item: FileManagerItem) => void;\n  onItemDoubleClick: (e: MouseEvent, item: FileManagerItem) => void;\n  onItemContextMenu: (e: MouseEvent, item: FileManagerItem) => void;\n  onRenameCommit: (id: string, nextName: string) => void;\n  onRenameCancel: () => void;\n  onItemDragStart?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragOver?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragLeave?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDrop?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragEnd?: (e: DragEvent, item: FileManagerItem) => void;\n  // Container handlers (for whitespace context-menu + drop)\n  onContainerContextMenu?: (e: MouseEvent) => void;\n  onContainerDragEnter?: (e: DragEvent) => void;\n  onContainerDragOver?: (e: DragEvent) => void;\n  onContainerDragLeave?: (e: DragEvent) => void;\n  onContainerDrop?: (e: DragEvent) => void;\n}\n\nconst TRACK_PX_BY_SIZE: Record<FileManagerIconSize, number> = {\n  sm: 80,\n  md: 120,\n  lg: 180,\n};\n\nexport function FileManagerContentPane(props: FileManagerContentPaneProps) {\n  const {\n    items,\n    viewMode,\n    iconSize,\n    rowHeight,\n    sort,\n    onSetSort,\n    renamingId,\n    validateRename,\n    iconForNode,\n    enableInternalDrag,\n    enableMarqueeSelection,\n    dropTargetId,\n    dropInvalid,\n    loading,\n    loadError,\n    onRetryLoad,\n    isEmpty,\n    marqueeRect,\n    marqueeActive,\n    onMarqueePointerDown,\n    scrollRef,\n    columnsRef,\n    virtualize,\n    virtualizeThreshold,\n    renderItem,\n    renderEmpty,\n    renderLoading,\n    labels,\n    onItemClick,\n    onItemDoubleClick,\n    onItemContextMenu,\n    onRenameCommit,\n    onRenameCancel,\n    onItemDragStart,\n    onItemDragOver,\n    onItemDragLeave,\n    onItemDrop,\n    onItemDragEnd,\n    onContainerContextMenu,\n    onContainerDragEnter,\n    onContainerDragOver,\n    onContainerDragLeave,\n    onContainerDrop,\n  } = props;\n\n  // Compute column count for keyboard arrow nav (grid mode)\n  const recomputeColumns = useCallback(() => {\n    const el = scrollRef.current;\n    if (!el || viewMode !== \"grid\") {\n      columnsRef.current = 1;\n      return;\n    }\n    const trackPx = TRACK_PX_BY_SIZE[iconSize];\n    const gap = 4; // matches `gap-1` in grid view\n    const availableWidth = el.clientWidth - 16; // p-2 padding\n    const cols = Math.max(\n      1,\n      Math.floor((availableWidth + gap) / (trackPx + gap)),\n    );\n    columnsRef.current = cols;\n  }, [scrollRef, viewMode, iconSize, columnsRef]);\n\n  useEffect(() => {\n    recomputeColumns();\n    const el = scrollRef.current;\n    if (!el) return;\n    const ro = new ResizeObserver(recomputeColumns);\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [recomputeColumns, scrollRef]);\n\n  // Pointer-down dispatch — only fire marquee if NOT on an item\n  const handlePointerDown = (e: PointerEvent<HTMLDivElement>) => {\n    if (!enableMarqueeSelection) return;\n    const target = e.target as HTMLElement | null;\n    if (target?.closest(\"[data-item-id]\")) return; // item handles its own drag\n    onMarqueePointerDown(e);\n  };\n\n  return (\n    <div\n      ref={scrollRef}\n      data-file-manager-scroller\n      className=\"relative h-full flex-1 overflow-y-auto overflow-x-hidden\"\n      onPointerDown={handlePointerDown}\n      onContextMenu={onContainerContextMenu}\n      onDragEnter={onContainerDragEnter}\n      onDragOver={onContainerDragOver}\n      onDragLeave={onContainerDragLeave}\n      onDrop={onContainerDrop}\n      style={{ userSelect: marqueeActive ? \"none\" : undefined }}\n    >\n      {loading ? (\n        renderLoading?.() ?? <FileManagerLoading />\n      ) : loadError ? (\n        <div className=\"flex h-full items-center justify-center px-4 py-8 text-center\">\n          <div className=\"space-y-2\">\n            <p className=\"text-sm font-medium text-destructive\">\n              {labels.loadError}\n            </p>\n            <p className=\"text-xs text-muted-foreground\">{loadError}</p>\n            <button\n              type=\"button\"\n              onClick={onRetryLoad}\n              className=\"rounded-sm border border-border px-2 py-1 text-xs hover:bg-muted/60\"\n            >\n              {labels.retry}\n            </button>\n          </div>\n        </div>\n      ) : isEmpty ? (\n        renderEmpty?.() ?? <FileManagerEmpty />\n      ) : viewMode === \"grid\" ? (\n        <FileManagerGridView\n          items={items}\n          iconSize={iconSize}\n          renamingId={renamingId}\n          validateRename={validateRename}\n          iconForNode={iconForNode}\n          enableInternalDrag={enableInternalDrag}\n          dropTargetId={dropTargetId}\n          dropInvalid={dropInvalid}\n          renderItem={renderItem}\n          onItemClick={onItemClick}\n          onItemDoubleClick={onItemDoubleClick}\n          onItemContextMenu={onItemContextMenu}\n          onRenameCommit={onRenameCommit}\n          onRenameCancel={onRenameCancel}\n          onItemDragStart={onItemDragStart}\n          onItemDragOver={onItemDragOver}\n          onItemDragLeave={onItemDragLeave}\n          onItemDrop={onItemDrop}\n          onItemDragEnd={onItemDragEnd}\n        />\n      ) : (\n        <FileManagerListView\n          items={items}\n          rowHeight={rowHeight}\n          sort={sort}\n          onSetSort={onSetSort}\n          renamingId={renamingId}\n          validateRename={validateRename}\n          iconForNode={iconForNode}\n          enableInternalDrag={enableInternalDrag}\n          dropTargetId={dropTargetId}\n          dropInvalid={dropInvalid}\n          renderItem={renderItem}\n          labels={labels}\n          scrollRef={scrollRef}\n          virtualize={virtualize}\n          virtualizeThreshold={virtualizeThreshold}\n          onItemClick={onItemClick}\n          onItemDoubleClick={onItemDoubleClick}\n          onItemContextMenu={onItemContextMenu}\n          onRenameCommit={onRenameCommit}\n          onRenameCancel={onRenameCancel}\n          onItemDragStart={onItemDragStart}\n          onItemDragOver={onItemDragOver}\n          onItemDragLeave={onItemDragLeave}\n          onItemDrop={onItemDrop}\n          onItemDragEnd={onItemDragEnd}\n        />\n      )}\n      <FileManagerMarquee rect={marqueeRect} />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-content-pane.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-grid-view.tsx",
      "content": "\"use client\";\n\nimport type { DragEvent, MouseEvent, ReactNode } from \"react\";\nimport type {\n  FileManagerIconSize,\n  FileManagerItem,\n  FileManagerItemRenderArgs,\n  FileManagerValidateRenameArgs,\n  FsNode,\n} from \"../types\";\nimport { FileManagerItemView } from \"./file-manager-item\";\n\nconst GRID_TRACK_PX: Record<FileManagerIconSize, number> = {\n  sm: 80,\n  md: 120,\n  lg: 180,\n};\n\ninterface FileManagerGridViewProps {\n  items: FileManagerItem[];\n  iconSize: FileManagerIconSize;\n  renamingId: string | null;\n  validateRename?: (args: FileManagerValidateRenameArgs) => string | null;\n  iconForNode?: (args: { node: FsNode }) => ReactNode;\n  enableInternalDrag: boolean;\n  dropTargetId: string | null;\n  dropInvalid: boolean;\n  renderItem?: (args: FileManagerItemRenderArgs) => ReactNode;\n  onItemClick: (e: MouseEvent, item: FileManagerItem) => void;\n  onItemDoubleClick: (e: MouseEvent, item: FileManagerItem) => void;\n  onItemContextMenu: (e: MouseEvent, item: FileManagerItem) => void;\n  onRenameCommit: (id: string, nextName: string) => void;\n  onRenameCancel: () => void;\n  onItemDragStart?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragOver?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragLeave?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDrop?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragEnd?: (e: DragEvent, item: FileManagerItem) => void;\n}\n\nexport function FileManagerGridView(props: FileManagerGridViewProps) {\n  const {\n    items,\n    iconSize,\n    renamingId,\n    validateRename,\n    iconForNode,\n    enableInternalDrag,\n    dropTargetId,\n    dropInvalid,\n    renderItem,\n    onItemClick,\n    onItemDoubleClick,\n    onItemContextMenu,\n    onRenameCommit,\n    onRenameCancel,\n    onItemDragStart,\n    onItemDragOver,\n    onItemDragLeave,\n    onItemDrop,\n    onItemDragEnd,\n  } = props;\n\n  const trackPx = GRID_TRACK_PX[iconSize];\n\n  return (\n    <div\n      role=\"grid\"\n      aria-multiselectable=\"true\"\n      className=\"grid gap-1 p-2\"\n      style={{\n        gridTemplateColumns: `repeat(auto-fill, minmax(${trackPx}px, 1fr))`,\n      }}\n    >\n      {items.map((item) => {\n        const isDropTarget = dropTargetId === item.node.id;\n        const defaultItem = (\n          <FileManagerItemView\n            item={item}\n            viewMode=\"grid\"\n            iconSize={iconSize}\n            isRenaming={renamingId === item.node.id}\n            validateRename={validateRename}\n            iconForNode={iconForNode}\n            enableInternalDrag={enableInternalDrag}\n            isDropTarget={isDropTarget}\n            dropInvalid={isDropTarget && dropInvalid}\n            onClick={onItemClick}\n            onDoubleClick={onItemDoubleClick}\n            onContextMenu={onItemContextMenu}\n            onRenameCommit={onRenameCommit}\n            onRenameCancel={onRenameCancel}\n            onDragStart={onItemDragStart}\n            onDragOver={onItemDragOver}\n            onDragLeave={onItemDragLeave}\n            onDrop={onItemDrop}\n            onDragEnd={onItemDragEnd}\n          />\n        );\n        // v0.1.1 (review): `role=\"row\"` on each cell wrapper — the grid\n        // previously nested gridcells under unroled divs (invalid\n        // grid → gridcell without a row). Matches the list view's shape.\n        if (renderItem) {\n          return (\n            <div key={item.node.id} role=\"row\">\n              {renderItem({ item, defaultItem, viewMode: \"grid\" })}\n            </div>\n          );\n        }\n        return (\n          <div key={item.node.id} role=\"row\">\n            {defaultItem}\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-grid-view.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-list-view.tsx",
      "content": "\"use client\";\n\nimport {\n  useEffect,\n  useState,\n  type CSSProperties,\n  type DragEvent,\n  type MouseEvent,\n  type ReactNode,\n  type RefObject,\n} from \"react\";\nimport { ChevronDown, ChevronUp, ChevronsUpDown } from \"lucide-react\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport { cn } from \"@/lib/utils\";\nimport type {\n  FileManagerItem,\n  FileManagerItemRenderArgs,\n  FileManagerLabels,\n  FileManagerSortKey,\n  FileManagerSortState,\n  FileManagerValidateRenameArgs,\n  FileManagerVirtualizeMode,\n  FsNode,\n} from \"../types\";\nimport { FileManagerItemView } from \"./file-manager-item\";\n\ninterface FileManagerListViewProps {\n  items: FileManagerItem[];\n  rowHeight: number;\n  sort: FileManagerSortState;\n  onSetSort: (next: FileManagerSortState) => void;\n  renamingId: string | null;\n  validateRename?: (args: FileManagerValidateRenameArgs) => string | null;\n  iconForNode?: (args: { node: FsNode }) => ReactNode;\n  enableInternalDrag: boolean;\n  dropTargetId: string | null;\n  dropInvalid: boolean;\n  renderItem?: (args: FileManagerItemRenderArgs) => ReactNode;\n  labels: FileManagerLabels;\n  /** Scroll container ref. Required for virtualization measurements. */\n  scrollRef: RefObject<HTMLDivElement | null>;\n  /** Virtualization mode: 'auto' engages above `virtualizeThreshold`. */\n  virtualize: FileManagerVirtualizeMode;\n  virtualizeThreshold: number;\n  // shared callbacks\n  onItemClick: (e: MouseEvent, item: FileManagerItem) => void;\n  onItemDoubleClick: (e: MouseEvent, item: FileManagerItem) => void;\n  onItemContextMenu: (e: MouseEvent, item: FileManagerItem) => void;\n  onRenameCommit: (id: string, nextName: string) => void;\n  onRenameCancel: () => void;\n  onItemDragStart?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragOver?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragLeave?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDrop?: (e: DragEvent, item: FileManagerItem) => void;\n  onItemDragEnd?: (e: DragEvent, item: FileManagerItem) => void;\n}\n\nfunction HeaderCell({\n  active,\n  order,\n  onClick,\n  className,\n  children,\n}: {\n  active: boolean;\n  order: \"asc\" | \"desc\";\n  onClick: () => void;\n  className?: string;\n  children: ReactNode;\n}) {\n  return (\n    <button\n      type=\"button\"\n      role=\"columnheader\"\n      aria-sort={\n        active ? (order === \"asc\" ? \"ascending\" : \"descending\") : \"none\"\n      }\n      onClick={onClick}\n      className={cn(\n        \"flex items-center gap-1 truncate text-left text-xs font-medium uppercase tracking-wide text-muted-foreground hover:text-foreground\",\n        className,\n      )}\n    >\n      {children}\n      {active ? (\n        order === \"asc\" ? (\n          <ChevronUp className=\"size-3\" aria-hidden=\"true\" />\n        ) : (\n          <ChevronDown className=\"size-3\" aria-hidden=\"true\" />\n        )\n      ) : (\n        <ChevronsUpDown className=\"size-3 opacity-40\" aria-hidden=\"true\" />\n      )}\n    </button>\n  );\n}\n\nexport function FileManagerListView(props: FileManagerListViewProps) {\n  const {\n    items,\n    rowHeight,\n    sort,\n    onSetSort,\n    renamingId,\n    validateRename,\n    iconForNode,\n    enableInternalDrag,\n    dropTargetId,\n    dropInvalid,\n    renderItem,\n    labels,\n    scrollRef,\n    virtualize,\n    virtualizeThreshold,\n    onItemClick,\n    onItemDoubleClick,\n    onItemContextMenu,\n    onRenameCommit,\n    onRenameCancel,\n    onItemDragStart,\n    onItemDragOver,\n    onItemDragLeave,\n    onItemDrop,\n    onItemDragEnd,\n  } = props;\n\n  // Own the virtualizer locally — keeps virtualizer state inside this\n  // component's scope so React Compiler doesn't strand stale measurements\n  // across cross-component prop boundaries.\n  const virtualizationActive =\n    virtualize === \"always\" ||\n    (virtualize === \"auto\" && items.length >= virtualizeThreshold);\n\n  // Sync the scroll element ref into local state so `useVirtualizer` picks it\n  // up on the second render (refs alone don't trigger re-render, which leaves\n  // the virtualizer with a null scroll element and zero measured viewport).\n  const [scrollEl, setScrollEl] = useState<HTMLDivElement | null>(\n    () => scrollRef.current,\n  );\n  useEffect(() => {\n    if (scrollRef.current && scrollRef.current !== scrollEl) {\n      setScrollEl(scrollRef.current);\n    }\n  }, [scrollRef, scrollEl]);\n\n  const virtualizer = useVirtualizer<HTMLDivElement, HTMLDivElement>({\n    count: items.length,\n    getScrollElement: () => scrollEl,\n    estimateSize: () => rowHeight,\n    overscan: 8,\n    enabled: virtualizationActive,\n  });\n\n  const toggleSort = (key: FileManagerSortKey) => {\n    if (sort.key === key) {\n      onSetSort({ key, order: sort.order === \"asc\" ? \"desc\" : \"asc\" });\n    } else {\n      onSetSort({ key, order: \"asc\" });\n    }\n  };\n\n  const headerRow = (\n    <div\n      role=\"row\"\n      className=\"sticky top-0 z-10 flex h-8 items-center gap-2 border-b border-border/60 bg-card/90 px-2 backdrop-blur-sm\"\n    >\n      <span aria-hidden=\"true\" className=\"size-4 shrink-0\" />\n      <HeaderCell\n        active={sort.key === \"name\"}\n        order={sort.order}\n        onClick={() => toggleSort(\"name\")}\n        className=\"flex-1\"\n      >\n        {labels.columnName}\n      </HeaderCell>\n      <HeaderCell\n        active={sort.key === \"modified\"}\n        order={sort.order}\n        onClick={() => toggleSort(\"modified\")}\n        className=\"hidden w-32 shrink-0 sm:flex\"\n      >\n        {labels.columnModified}\n      </HeaderCell>\n      <HeaderCell\n        active={sort.key === \"size\"}\n        order={sort.order}\n        onClick={() => toggleSort(\"size\")}\n        className=\"hidden w-20 shrink-0 justify-end sm:flex\"\n      >\n        {labels.columnSize}\n      </HeaderCell>\n      <HeaderCell\n        active={sort.key === \"type\"}\n        order={sort.order}\n        onClick={() => toggleSort(\"type\")}\n        className=\"hidden w-16 shrink-0 md:flex\"\n      >\n        {labels.columnType}\n      </HeaderCell>\n    </div>\n  );\n\n  const buildRow = (item: FileManagerItem, outerStyle?: CSSProperties) => {\n    const isDropTarget = dropTargetId === item.node.id;\n    const defaultItem = (\n      <FileManagerItemView\n        item={item}\n        viewMode=\"list\"\n        iconSize=\"sm\"\n        isRenaming={renamingId === item.node.id}\n        validateRename={validateRename}\n        iconForNode={iconForNode}\n        enableInternalDrag={enableInternalDrag}\n        isDropTarget={isDropTarget}\n        dropInvalid={isDropTarget && dropInvalid}\n        onClick={onItemClick}\n        onDoubleClick={onItemDoubleClick}\n        onContextMenu={onItemContextMenu}\n        onRenameCommit={onRenameCommit}\n        onRenameCancel={onRenameCancel}\n        onDragStart={onItemDragStart}\n        onDragOver={onItemDragOver}\n        onDragLeave={onItemDragLeave}\n        onDrop={onItemDrop}\n        onDragEnd={onItemDragEnd}\n        outerStyle={\n          outerStyle\n            ? { ...outerStyle, height: `${rowHeight}px` }\n            : { height: `${rowHeight}px` }\n        }\n      />\n    );\n    if (renderItem) {\n      return (\n        <div\n          key={item.node.id}\n          style={outerStyle}\n          role=\"row\"\n        >\n          {renderItem({ item, defaultItem, viewMode: \"list\" })}\n        </div>\n      );\n    }\n    return (\n      <div\n        key={item.node.id}\n        role=\"row\"\n      >\n        {defaultItem}\n      </div>\n    );\n  };\n\n  if (virtualizationActive) {\n    const totalHeight = virtualizer.getTotalSize();\n    const virtualItems = virtualizer.getVirtualItems();\n    return (\n      <div role=\"grid\" aria-multiselectable=\"true\">\n        {headerRow}\n        <div style={{ height: `${totalHeight}px`, position: \"relative\" }}>\n          {virtualItems.map((vi) => {\n            const item = items[vi.index];\n            if (!item) return null;\n            const outerStyle: CSSProperties = {\n              position: \"absolute\",\n              top: 0,\n              left: 0,\n              width: \"100%\",\n              transform: `translateY(${vi.start}px)`,\n            };\n            return buildRow(item, outerStyle);\n          })}\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div role=\"grid\" aria-multiselectable=\"true\">\n      {headerRow}\n      <div className=\"flex flex-col\">\n        {items.map((item) => buildRow(item))}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-list-view.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-item.tsx",
      "content": "\"use client\";\n\nimport type {\n  CSSProperties,\n  DragEvent,\n  MouseEvent,\n  ReactNode,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport type {\n  FileManagerIconSize,\n  FileManagerItem,\n  FileManagerValidateRenameArgs,\n  FileManagerViewMode,\n  FsNode,\n} from \"../types\";\nimport { defaultFileIcon, defaultFolderIcon } from \"../lib/icons\";\nimport { formatBytes, formatDate, formatKind } from \"../lib/format\";\nimport { getNodeExtension } from \"../lib/icons\";\nimport { FileManagerRenameInput } from \"./file-manager-rename-input\";\n\nconst ICON_SIZE_PX: Record<FileManagerIconSize, number> = {\n  sm: 32,\n  md: 48,\n  lg: 72,\n};\n\nconst GRID_TRACK_PX: Record<FileManagerIconSize, number> = {\n  sm: 80,\n  md: 120,\n  lg: 180,\n};\n\ninterface FileManagerItemViewProps {\n  item: FileManagerItem;\n  viewMode: FileManagerViewMode;\n  iconSize: FileManagerIconSize;\n  isRenaming: boolean;\n  validateRename?: (args: FileManagerValidateRenameArgs) => string | null;\n  iconForNode?: (args: { node: FsNode }) => ReactNode;\n  enableInternalDrag: boolean;\n  /** Drop hover indicator state (only set on the hovered drop target). */\n  isDropTarget?: boolean;\n  dropInvalid?: boolean;\n  // shared callbacks\n  onClick: (e: MouseEvent, item: FileManagerItem) => void;\n  onDoubleClick: (e: MouseEvent, item: FileManagerItem) => void;\n  onContextMenu: (e: MouseEvent, item: FileManagerItem) => void;\n  onRenameCommit: (id: string, nextName: string) => void;\n  onRenameCancel: () => void;\n  onDragStart?: (e: DragEvent, item: FileManagerItem) => void;\n  onDragOver?: (e: DragEvent, item: FileManagerItem) => void;\n  onDragLeave?: (e: DragEvent, item: FileManagerItem) => void;\n  onDrop?: (e: DragEvent, item: FileManagerItem) => void;\n  onDragEnd?: (e: DragEvent, item: FileManagerItem) => void;\n  /** Used by virtualized list mode to position rows absolutely. */\n  outerStyle?: CSSProperties;\n}\n\nexport function FileManagerItemView(props: FileManagerItemViewProps) {\n  const {\n    item,\n    viewMode,\n    iconSize,\n    isRenaming,\n    validateRename,\n    iconForNode,\n    enableInternalDrag,\n    isDropTarget,\n    dropInvalid,\n    onClick,\n    onDoubleClick,\n    onContextMenu,\n    onRenameCommit,\n    onRenameCancel,\n    onDragStart,\n    onDragOver,\n    onDragLeave,\n    onDrop,\n    onDragEnd,\n    outerStyle,\n  } = props;\n\n  const { node, selected, focused, cut } = item;\n  const isFolder = node.type === \"folder\";\n  const ext = getNodeExtension(node);\n\n  const baseIcon = node.icon\n    ? node.icon\n    : iconForNode\n      ? iconForNode({ node })\n      : isFolder\n        ? defaultFolderIcon(false, \"size-full\")\n        : defaultFileIcon(node, \"size-full\");\n\n  const sharedProps = {\n    \"data-item-id\": node.id,\n    role: \"gridcell\" as const,\n    tabIndex: focused ? 0 : -1,\n    \"aria-selected\": selected,\n    draggable: enableInternalDrag && !isRenaming,\n    onClick: (e: MouseEvent) => onClick(e, item),\n    onDoubleClick: (e: MouseEvent) => onDoubleClick(e, item),\n    onContextMenu: (e: MouseEvent) => onContextMenu(e, item),\n    onDragStart: (e: DragEvent) =>\n      enableInternalDrag && onDragStart && onDragStart(e, item),\n    onDragOver: (e: DragEvent) => onDragOver?.(e, item),\n    onDragLeave: (e: DragEvent) => onDragLeave?.(e, item),\n    onDrop: (e: DragEvent) => onDrop?.(e, item),\n    onDragEnd: (e: DragEvent) => onDragEnd?.(e, item),\n  };\n\n  if (viewMode === \"grid\") {\n    const trackPx = GRID_TRACK_PX[iconSize];\n    const iconPx = ICON_SIZE_PX[iconSize];\n    return (\n      <div\n        {...sharedProps}\n        style={outerStyle}\n        className={cn(\n          \"group relative flex cursor-pointer select-none flex-col items-center gap-1.5 rounded-md p-2 outline-none transition-colors\",\n          selected\n            ? \"bg-secondary text-secondary-foreground\"\n            : \"hover:bg-muted/60\",\n          focused &&\n            \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset\",\n          isDropTarget &&\n            !dropInvalid &&\n            \"ring-2 ring-primary\",\n          isDropTarget && dropInvalid && \"ring-2 ring-destructive\",\n          cut && \"opacity-50\",\n        )}\n      >\n        <div\n          aria-hidden=\"true\"\n          className={cn(\n            \"flex items-center justify-center opacity-80 transition-opacity\",\n            (selected || focused) && \"opacity-100\",\n            \"group-hover:opacity-100\",\n            isFolder\n              ? \"text-amber-600 dark:text-amber-400\"\n              : \"text-muted-foreground\",\n            selected && \"text-secondary-foreground\",\n          )}\n          style={{ width: iconPx, height: iconPx }}\n        >\n          {baseIcon}\n        </div>\n        {isRenaming ? (\n          <div style={{ width: trackPx - 8 }}>\n            <FileManagerRenameInput\n              node={node}\n              validateRename={validateRename}\n              onCommit={(next) => onRenameCommit(node.id, next)}\n              onCancel={onRenameCancel}\n            />\n          </div>\n        ) : (\n          <span\n            className=\"line-clamp-2 max-w-full text-center text-xs\"\n            title={node.name}\n            style={{ maxWidth: trackPx - 4 }}\n          >\n            {node.name}\n          </span>\n        )}\n      </div>\n    );\n  }\n\n  // List mode\n  return (\n    <div\n      {...sharedProps}\n      style={outerStyle}\n      className={cn(\n        \"group flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 text-sm outline-none transition-colors\",\n        selected\n          ? \"bg-secondary text-secondary-foreground\"\n          : \"hover:bg-muted/60\",\n        focused &&\n          \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset\",\n        isDropTarget &&\n          !dropInvalid &&\n          \"ring-2 ring-primary\",\n        isDropTarget && dropInvalid && \"ring-2 ring-destructive\",\n        cut && \"opacity-50\",\n      )}\n    >\n      <span\n        aria-hidden=\"true\"\n        className={cn(\n          \"flex shrink-0 items-center justify-center opacity-80 transition-opacity group-hover:opacity-100\",\n          (selected || focused) && \"opacity-100\",\n          isFolder\n            ? \"text-amber-600 dark:text-amber-400\"\n            : \"text-muted-foreground\",\n          selected && \"text-secondary-foreground\",\n        )}\n      >\n        {baseIcon}\n      </span>\n      <span className=\"min-w-0 flex-1 truncate\" title={node.name}>\n        {isRenaming ? (\n          <FileManagerRenameInput\n            node={node}\n            validateRename={validateRename}\n            onCommit={(next) => onRenameCommit(node.id, next)}\n            onCancel={onRenameCancel}\n          />\n        ) : (\n          node.name\n        )}\n      </span>\n      <span className=\"hidden w-32 shrink-0 text-xs text-muted-foreground sm:block\">\n        {formatDate(node.modifiedAt)}\n      </span>\n      <span className=\"hidden w-20 shrink-0 text-right font-mono text-xs text-muted-foreground sm:block\">\n        {isFolder ? \"—\" : formatBytes(node.size ?? 0)}\n      </span>\n      <span className=\"hidden w-16 shrink-0 text-xs text-muted-foreground md:block\">\n        {formatKind(ext, node.type)}\n      </span>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-item.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-rename-input.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport type { FsNode, FileManagerValidateRenameArgs } from \"../types\";\n\ninterface FileManagerRenameInputProps {\n  node: FsNode;\n  onCommit: (nextName: string) => void;\n  onCancel: () => void;\n  validateRename?: (args: FileManagerValidateRenameArgs) => string | null;\n  className?: string;\n}\n\nexport function FileManagerRenameInput(props: FileManagerRenameInputProps) {\n  const { node, onCommit, onCancel, validateRename, className } = props;\n  const [value, setValue] = useState(node.name);\n  const [error, setError] = useState<string | null>(null);\n  const inputRef = useRef<HTMLInputElement>(null);\n\n  useEffect(() => {\n    const input = inputRef.current;\n    if (!input) return;\n    input.focus();\n    if (node.type === \"folder\") {\n      input.select();\n    } else {\n      const dot = node.name.lastIndexOf(\".\");\n      if (dot > 0) input.setSelectionRange(0, dot);\n      else input.select();\n    }\n  }, [node.type, node.name]);\n\n  const tryCommit = () => {\n    const trimmed = value.trim();\n    if (trimmed === \"\") {\n      setError(\"Name cannot be empty\");\n      return;\n    }\n    if (trimmed === node.name) {\n      onCancel();\n      return;\n    }\n    if (validateRename) {\n      const message = validateRename({ node, nextName: trimmed });\n      if (message) {\n        setError(message);\n        return;\n      }\n    }\n    onCommit(trimmed);\n  };\n\n  return (\n    <div className={cn(\"relative flex flex-1 min-w-0 items-center\", className)}>\n      <input\n        ref={inputRef}\n        type=\"text\"\n        value={value}\n        onChange={(e) => {\n          setValue(e.target.value);\n          if (error) setError(null);\n        }}\n        onKeyDown={(e) => {\n          if (e.key === \"Enter\") {\n            e.preventDefault();\n            e.stopPropagation();\n            tryCommit();\n          } else if (e.key === \"Escape\") {\n            e.preventDefault();\n            e.stopPropagation();\n            onCancel();\n          } else {\n            e.stopPropagation();\n          }\n        }}\n        onBlur={() => tryCommit()}\n        onClick={(e) => e.stopPropagation()}\n        onDoubleClick={(e) => e.stopPropagation()}\n        onPointerDown={(e) => e.stopPropagation()}\n        aria-label={`Rename ${node.name}`}\n        aria-invalid={error ? true : undefined}\n        className={cn(\n          \"h-6 min-w-0 flex-1 rounded-sm border bg-background px-1 text-sm outline-none\",\n          \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background\",\n          error\n            ? \"border-destructive ring-1 ring-destructive\"\n            : \"border-border\",\n        )}\n      />\n      {error ? (\n        <span\n          role=\"alert\"\n          className=\"pointer-events-none absolute left-0 top-full z-20 mt-0.5 rounded-sm bg-destructive px-1.5 py-0.5 text-xs text-destructive-foreground shadow-sm\"\n        >\n          {error}\n        </span>\n      ) : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-rename-input.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-context-menu.tsx",
      "content": "\"use client\";\n\nimport { useMemo, useState, type MouseEvent, type ReactNode } from \"react\";\nimport {\n  ClipboardCopy,\n  ClipboardPaste,\n  FilePlus,\n  FolderPlus,\n  Pencil,\n  RefreshCw,\n  Scissors,\n  Trash2,\n} from \"lucide-react\";\nimport {\n  ContextMenu,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuSeparator,\n  ContextMenuTrigger,\n} from \"@/components/ui/context-menu\";\nimport type {\n  FileManagerActions,\n  FileManagerContextMenuContext,\n  FileManagerContextMenuItem,\n  FileManagerLabels,\n  FileManagerState,\n  FsNode,\n} from \"../types\";\n\ninterface FileManagerContextMenuProps {\n  state: FileManagerState;\n  actions: FileManagerActions;\n  enabled: boolean;\n  labels: FileManagerLabels;\n  gates: {\n    open: boolean;\n    newFile: boolean;\n    newFolder: boolean;\n    cut: boolean;\n    copy: boolean;\n    paste: boolean;\n    rename: boolean;\n    delete: boolean;\n    refresh: boolean;\n  };\n  wired: {\n    onOpen: boolean;\n    onCreate: boolean;\n    onRename: boolean;\n    onDelete: boolean;\n    onMove: boolean;\n    onPaste: boolean;\n    onRefresh: boolean;\n  };\n  nodeIndex: ReadonlyMap<string, FsNode>;\n  renderContextMenu?: (ctx: FileManagerContextMenuContext) => ReactNode;\n  children: ReactNode;\n}\n\nexport function FileManagerContextMenu(props: FileManagerContextMenuProps) {\n  const {\n    state,\n    actions,\n    enabled,\n    labels,\n    gates,\n    wired,\n    nodeIndex,\n    renderContextMenu,\n    children,\n  } = props;\n  const [target, setTarget] = useState<{\n    node: FsNode | null;\n    position: { x: number; y: number };\n  } | null>(null);\n\n  const captureTarget = (e: MouseEvent) => {\n    const el = e.target as HTMLElement | null;\n    const itemEl = el?.closest(\"[data-item-id]\") as HTMLElement | null;\n    const id = itemEl?.getAttribute(\"data-item-id\") ?? null;\n    const node = id ? (nodeIndex.get(id) ?? null) : null;\n    setTarget({ node, position: { x: e.clientX, y: e.clientY } });\n  };\n\n  const defaultActions = useMemo<FileManagerContextMenuItem[]>(() => {\n    if (!target) return [];\n    const node = target.node;\n    const list: FileManagerContextMenuItem[] = [];\n    const hasClipboard = state.clipboard.kind !== null;\n    const ids =\n      node && state.selectedIds.has(node.id)\n        ? Array.from(state.selectedIds)\n        : node\n          ? [node.id]\n          : [];\n\n    if (gates.open && wired.onOpen && node && node.type === \"file\") {\n      list.push({\n        id: \"open\",\n        label: labels.contextOpen,\n        icon: <FilePlus className=\"size-3.5\" />,\n        onSelect: () => actions.triggerOpen(node.id),\n      });\n    }\n    if (gates.newFile && wired.onCreate) {\n      list.push({\n        id: \"new-file\",\n        label: labels.contextNewFile,\n        icon: <FilePlus className=\"size-3.5\" />,\n        onSelect: () => actions.triggerCreate(\"file\"),\n      });\n    }\n    if (gates.newFolder && wired.onCreate) {\n      list.push({\n        id: \"new-folder\",\n        label: labels.contextNewFolder,\n        icon: <FolderPlus className=\"size-3.5\" />,\n        onSelect: () => actions.triggerCreate(\"folder\"),\n      });\n    }\n    if (gates.cut && node && ids.length > 0) {\n      list.push({\n        id: \"cut\",\n        label: labels.contextCut,\n        icon: <Scissors className=\"size-3.5\" />,\n        onSelect: () => actions.cut(ids),\n      });\n    }\n    if (gates.copy && node && ids.length > 0) {\n      list.push({\n        id: \"copy\",\n        label: labels.contextCopy,\n        icon: <ClipboardCopy className=\"size-3.5\" />,\n        onSelect: () => actions.copy(ids),\n      });\n    }\n    if (gates.paste && wired.onPaste) {\n      list.push({\n        id: \"paste\",\n        label: labels.contextPaste,\n        icon: <ClipboardPaste className=\"size-3.5\" />,\n        disabled: !hasClipboard,\n        onSelect: () => actions.paste(),\n      });\n    }\n    if (gates.rename && wired.onRename && node) {\n      list.push({\n        id: \"rename\",\n        label: labels.contextRename,\n        icon: <Pencil className=\"size-3.5\" />,\n        onSelect: () => actions.startRename(node.id),\n      });\n    }\n    if (gates.refresh && wired.onRefresh) {\n      list.push({\n        id: \"refresh\",\n        label: labels.contextRefresh,\n        icon: <RefreshCw className=\"size-3.5\" />,\n        onSelect: () => actions.refresh(state.currentFolderId),\n      });\n    }\n    if (gates.delete && wired.onDelete && node && ids.length > 0) {\n      list.push({\n        id: \"delete\",\n        label: labels.contextDelete,\n        icon: <Trash2 className=\"size-3.5\" />,\n        destructive: true,\n        onSelect: () => actions.triggerDelete(ids),\n      });\n    }\n    return list;\n  }, [target, state, gates, wired, labels, actions]);\n\n  if (!enabled) {\n    return <>{children}</>;\n  }\n\n  const ctx: FileManagerContextMenuContext = {\n    state,\n    actions,\n    node: target?.node ?? null,\n    defaultActions,\n    position: target?.position ?? { x: 0, y: 0 },\n    labels,\n  };\n\n  return (\n    <ContextMenu>\n      {/* F-cross-13: no `asChild` — Base UI triggers reject it. `display: contents`\n          keeps the trigger element out of layout (the wrapped div already used it);\n          right-clicks bubble up from children. */}\n      <ContextMenuTrigger className=\"contents\" onContextMenu={captureTarget}>\n        {children}\n      </ContextMenuTrigger>\n      <ContextMenuContent className=\"min-w-[180px]\">\n        {renderContextMenu ? (\n          renderContextMenu(ctx)\n        ) : defaultActions.length === 0 ? (\n          <ContextMenuItem disabled>{labels.emptyTitle}</ContextMenuItem>\n        ) : (\n          <DefaultItems items={defaultActions} />\n        )}\n      </ContextMenuContent>\n    </ContextMenu>\n  );\n}\n\nfunction DefaultItems({ items }: { items: FileManagerContextMenuItem[] }) {\n  const out: ReactNode[] = [];\n  let lastSep = false;\n  items.forEach((item, i) => {\n    if (item.destructive && i > 0 && !lastSep) {\n      out.push(<ContextMenuSeparator key={`sep-${i}`} />);\n      lastSep = true;\n    } else {\n      lastSep = false;\n    }\n    out.push(\n      <ContextMenuItem\n        key={item.id}\n        disabled={item.disabled}\n        variant={item.destructive ? \"destructive\" : \"default\"}\n        onSelect={() => item.onSelect()}\n      >\n        {item.icon}\n        {item.label}\n      </ContextMenuItem>,\n    );\n  });\n  return <>{out}</>;\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-context-menu.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-status-bar.tsx",
      "content": "\"use client\";\n\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\nimport { formatBytes } from \"../lib/format\";\n\nexport function FileManagerStatusBar() {\n  const { state, visibleCount, totalSize, labels } = useFileManager();\n  const selectedCount = state.selectedIds.size;\n\n  return (\n    <div\n      role=\"status\"\n      aria-live=\"polite\"\n      className=\"flex h-7 shrink-0 items-center gap-3 border-t border-border/60 bg-card/40 px-3 text-xs text-muted-foreground\"\n    >\n      <span>\n        {visibleCount} {labels.itemCount}\n      </span>\n      {selectedCount > 0 ? (\n        <span>\n          {selectedCount} {labels.itemCountSelected}\n        </span>\n      ) : null}\n      {totalSize > 0 ? (\n        <span className=\"ml-auto font-mono\">\n          {formatBytes(totalSize)} {labels.totalSize}\n        </span>\n      ) : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-status-bar.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-empty.tsx",
      "content": "\"use client\";\n\nimport { FilePlus, FolderPlus, Inbox } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\n\nexport function FileManagerEmpty() {\n  const { actions, showNewFile, showNewFolder, labels } = useFileManager();\n  return (\n    <div\n      className=\"flex h-full flex-col items-center justify-center gap-3 px-4 py-10 text-center text-sm text-muted-foreground\"\n      role=\"status\"\n    >\n      <Inbox aria-hidden=\"true\" className=\"size-8 opacity-60\" />\n      <p>{labels.emptyTitle}</p>\n      {(showNewFile || showNewFolder) && (\n        <div className=\"flex gap-2\">\n          {showNewFile ? (\n            <Button\n              type=\"button\"\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => actions.triggerCreate(\"file\")}\n            >\n              <FilePlus className=\"size-3.5\" aria-hidden=\"true\" />\n              {labels.newFile}\n            </Button>\n          ) : null}\n          {showNewFolder ? (\n            <Button\n              type=\"button\"\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => actions.triggerCreate(\"folder\")}\n            >\n              <FolderPlus className=\"size-3.5\" aria-hidden=\"true\" />\n              {labels.newFolder}\n            </Button>\n          ) : null}\n        </div>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-empty.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-loading.tsx",
      "content": "\"use client\";\n\nimport { useFileManager } from \"../hooks/use-file-manager-context\";\n\nexport function FileManagerLoading() {\n  const { labels } = useFileManager();\n  return (\n    <div\n      className=\"grid gap-2 p-3\"\n      style={{\n        gridTemplateColumns: \"repeat(auto-fill, minmax(120px, 1fr))\",\n      }}\n      role=\"status\"\n      aria-label={labels.loading}\n    >\n      {[0, 1, 2, 3, 4, 5, 6, 7].map((i) => (\n        <div key={i} className=\"flex flex-col items-center gap-2 p-2\">\n          <div\n            className=\"h-12 w-12 animate-pulse rounded-md bg-muted\"\n            style={{ animationDelay: `${i * 80}ms` }}\n          />\n          <div\n            className=\"h-3 w-3/4 animate-pulse rounded-sm bg-muted\"\n            style={{ animationDelay: `${i * 80 + 40}ms` }}\n          />\n        </div>\n      ))}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-loading.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-drag-overlay.tsx",
      "content": "\"use client\";\n\nimport { FileUp } from \"lucide-react\";\nimport type { FileManagerLabels } from \"../types\";\n\ninterface FileManagerDragOverlayProps {\n  visible: boolean;\n  labels: FileManagerLabels;\n}\n\nexport function FileManagerDragOverlay(props: FileManagerDragOverlayProps) {\n  const { visible, labels } = props;\n  if (!visible) return null;\n  return (\n    <div\n      className=\"pointer-events-none absolute inset-0 z-30 flex items-center justify-center rounded-md border-2 border-dashed border-primary/70 bg-background/85 backdrop-blur-[2px]\"\n      role=\"presentation\"\n    >\n      <div className=\"flex flex-col items-center gap-2 text-sm font-medium text-foreground\">\n        <FileUp className=\"size-6 text-primary\" aria-hidden=\"true\" />\n        <span>{labels.externalDropOverlay}</span>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-drag-overlay.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-delete-confirm.tsx",
      "content": "\"use client\";\n\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n} from \"@/components/ui/alert-dialog\";\nimport type { FsNode, FileManagerLabels } from \"../types\";\n\ninterface FileManagerDeleteConfirmProps {\n  open: boolean;\n  ids: string[];\n  nodes: FsNode[];\n  labels: FileManagerLabels;\n  onConfirm: () => void;\n  onCancel: () => void;\n}\n\nexport function FileManagerDeleteConfirm(\n  props: FileManagerDeleteConfirmProps,\n) {\n  const { open, ids, nodes, labels, onConfirm, onCancel } = props;\n  const count = ids.length;\n  const first = nodes[0]?.name ?? \"\";\n  const title =\n    count === 1\n      ? `Delete \"${first}\"?`\n      : labels.deleteConfirmTitle.replace(\"items\", `${count} items`);\n  const description =\n    count === 1\n      ? labels.deleteConfirmDescription\n      : `Delete \"${first}\" and ${count - 1} other${count - 1 > 1 ? \"s\" : \"\"}? ${labels.deleteConfirmDescription}`;\n\n  return (\n    <AlertDialog\n      open={open}\n      onOpenChange={(next) => {\n        if (!next) onCancel();\n      }}\n    >\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>{title}</AlertDialogTitle>\n          <AlertDialogDescription>{description}</AlertDialogDescription>\n        </AlertDialogHeader>\n        <AlertDialogFooter>\n          <AlertDialogCancel onClick={onCancel}>\n            {labels.deleteConfirmCancel}\n          </AlertDialogCancel>\n          <AlertDialogAction\n            onClick={onConfirm}\n            className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/40\"\n          >\n            {labels.deleteConfirmAction}\n          </AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-delete-confirm.tsx"
    },
    {
      "path": "src/registry/components/navigation/file-manager/parts/file-manager-marquee.tsx",
      "content": "\"use client\";\n\nimport type { MarqueeRect } from \"../hooks/use-marquee\";\n\ninterface FileManagerMarqueeProps {\n  rect: MarqueeRect | null;\n}\n\n/**\n * Visual rectangle drawn during marquee selection. Positioned absolutely\n * relative to the scroll container; consumer must place it inside a\n * `position: relative` ancestor (the content pane handles this).\n */\nexport function FileManagerMarquee(props: FileManagerMarqueeProps) {\n  const { rect } = props;\n  if (!rect) return null;\n  return (\n    <div\n      aria-hidden=\"true\"\n      className=\"pointer-events-none absolute z-10 rounded-sm border border-primary bg-primary/15\"\n      style={{\n        left: rect.left,\n        top: rect.top,\n        width: rect.width,\n        height: rect.height,\n      }}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/file-manager/parts/file-manager-marquee.tsx"
    }
  ],
  "categories": [
    "navigation",
    "filesystem",
    "explorer"
  ],
  "type": "registry:block"
}